1 /*        $NetBSD: tftp.c,v 1.38 2022/08/07 05:51:55 rin 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  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
21  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
22  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
23  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
25  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26  *
27  */
28 
29 /*
30  * Simple TFTP implementation for libsa.
31  * Assumes:
32  *  - socket descriptor (int) at open_file->f_devdata
33  *  - server host IP in global servip
34  * Restrictions:
35  *  - read only
36  *  - lseek only with SEEK_SET or SEEK_CUR
37  *  - no big time differences between transfers (<tftp timeout)
38  */
39 
40 /*
41  * XXX Does not currently implement:
42  * XXX
43  * XXX LIBSA_NO_FS_CLOSE
44  * XXX LIBSA_NO_FS_SEEK
45  * XXX LIBSA_NO_FS_WRITE
46  * XXX LIBSA_NO_FS_SYMLINK (does this even make sense?)
47  * XXX LIBSA_FS_SINGLECOMPONENT (does this even make sense?)
48  */
49 
50 #include <sys/types.h>
51 #include <sys/stat.h>
52 #include <netinet/in.h>
53 #include <netinet/udp.h>
54 #include <netinet/in_systm.h>
55 #include <lib/libkern/libkern.h>
56 
57 #include "stand.h"
58 #include "net.h"
59 
60 #include "tftp.h"
61 
62 extern struct in_addr servip;
63 
64 static int      tftpport = 2000;
65 
66 #define RSPACE 520            /* max data packet, rounded up */
67 
68 struct tftp_handle {
69           struct iodesc  *iodesc;
70           int             currblock;    /* contents of lastdata */
71           int             islastblock;  /* flag */
72           int             validsize;
73           int             off;
74           const char     *path;         /* saved for re-requests */
75           struct {
76                     u_char header[UDP_TOTAL_HEADER_SIZE];
77                     struct tftphdr t;
78                     u_char space[RSPACE];
79           } lastdata;
80 };
81 
82 static const int tftperrors[8] = {
83           0,                            /* ??? */
84           ENOENT,
85           EPERM,
86           ENOSPC,
87           EINVAL,                       /* ??? */
88           EINVAL,                       /* ??? */
89           EEXIST,
90           EINVAL,                       /* ??? */
91 };
92 
93 static ssize_t recvtftp(struct iodesc *, void *, size_t, saseconds_t);
94 static int tftp_makereq(struct tftp_handle *);
95 static int tftp_getnextblock(struct tftp_handle *);
96 #ifndef TFTP_NOTERMINATE
97 static void tftp_terminate(struct tftp_handle *);
98 #endif
99 static ssize_t tftp_size_of_file(struct tftp_handle *tftpfile);
100 
101 static ssize_t
recvtftp(struct iodesc * d,void * pkt,size_t len,saseconds_t tleft)102 recvtftp(struct iodesc *d, void *pkt, size_t len, saseconds_t tleft)
103 {
104           ssize_t n;
105           struct tftphdr *t;
106 
107           errno = 0;
108 
109           n = readudp(d, pkt, len, tleft);
110 
111           if (n < 4)
112                     return -1;
113 
114           t = (struct tftphdr *)pkt;
115           switch (ntohs(t->th_opcode)) {
116           case DATA:
117                     if (ntohs(t->th_block) != d->xid) {
118                               /*
119                                * Expected block?
120                                */
121                               return -1;
122                     }
123                     if (d->xid == 1) {
124                               /*
125                                * First data packet from new port.
126                                */
127                               struct udphdr *uh;
128                               uh = (struct udphdr *)pkt - 1;
129                               d->destport = uh->uh_sport;
130                     } /* else check uh_sport has not changed??? */
131                     return (n - (t->th_data - (char *)t));
132           case ERROR:
133                     if ((unsigned int)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
tftp_makereq(struct tftp_handle * h)153 tftp_makereq(struct tftp_handle *h)
154 {
155           struct {
156                     u_char header[UDP_TOTAL_HEADER_SIZE];
157                     struct tftphdr t;
158                     u_char space[FNAME_SIZE + 6];
159           } wbuf;
160           char           *wtail;
161           int             l;
162           ssize_t         res;
163           struct tftphdr *t;
164 
165           wbuf.t.th_opcode = htons((u_short)RRQ);
166           wtail = wbuf.t.th_stuff;
167           l = strlen(h->path);
168           (void)memcpy(wtail, h->path, l + 1);
169           wtail += l + 1;
170           (void)memcpy(wtail, "octet", 6);
171           wtail += 6;
172 
173           t = &h->lastdata.t;
174 
175           /* h->iodesc->myport = htons(--tftpport); */
176           h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff));
177           h->iodesc->destport = htons(IPPORT_TFTP);
178           h->iodesc->xid = 1; /* expected block */
179 
180           res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *)&wbuf.t,
181                            recvtftp, t, sizeof(*t) + RSPACE);
182 
183           if (res == -1)
184                     return errno;
185 
186           h->currblock = 1;
187           h->validsize = res;
188           h->islastblock = 0;
189           if (res < SEGSIZE)
190                     h->islastblock = 1; /* very short file */
191           return 0;
192 }
193 
194 /* ack block, expect next */
195 static int
tftp_getnextblock(struct tftp_handle * h)196 tftp_getnextblock(struct tftp_handle *h)
197 {
198           struct {
199                     u_char header[UDP_TOTAL_HEADER_SIZE];
200                     struct tftphdr t;
201           } wbuf;
202           char           *wtail;
203           int             res;
204           struct tftphdr *t;
205 
206           wbuf.t.th_opcode = htons((u_short)ACK);
207           wbuf.t.th_block = htons((u_short)h->currblock);
208           wtail = (char *)&wbuf.t.th_data;
209 
210           t = &h->lastdata.t;
211 
212           h->iodesc->xid = h->currblock + 1;      /* expected block */
213 
214           res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *)&wbuf.t,
215                            recvtftp, t, sizeof(*t) + RSPACE);
216 
217           if (res == -1)                /* 0 is OK! */
218                     return errno;
219 
220           h->currblock++;
221           h->validsize = res;
222           if (res < SEGSIZE)
223                     h->islastblock = 1; /* EOF */
224           return 0;
225 }
226 
227 #ifndef TFTP_NOTERMINATE
228 static void
tftp_terminate(struct tftp_handle * h)229 tftp_terminate(struct tftp_handle *h)
230 {
231           struct {
232                     u_char header[UDP_TOTAL_HEADER_SIZE];
233                     struct tftphdr t;
234           } wbuf;
235           char           *wtail;
236 
237           wtail = (char *)&wbuf.t.th_data;
238           if (h->islastblock) {
239                     wbuf.t.th_opcode = htons((u_short)ACK);
240                     wbuf.t.th_block = htons((u_short)h->currblock);
241           } else {
242                     wbuf.t.th_opcode = htons((u_short)ERROR);
243                     wbuf.t.th_code = htons((u_short)ENOSPACE); /* ??? */
244                     *wtail++ = '\0'; /* empty error string */
245           }
246 
247           (void)sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t);
248 }
249 #endif
250 
251 __compactcall int
tftp_open(const char * path,struct open_file * f)252 tftp_open(const char *path, struct open_file *f)
253 {
254           struct tftp_handle *tftpfile;
255           struct iodesc  *io;
256           int             res;
257 
258           tftpfile = (struct tftp_handle *)alloc(sizeof(*tftpfile));
259           if (!tftpfile)
260                     return ENOMEM;
261 
262           tftpfile->iodesc = io = socktodesc(*(int *)(f->f_devdata));
263           io->destip = servip;
264           tftpfile->off = 0;
265           tftpfile->path = path;        /* XXXXXXX we hope it's static */
266 
267           res = tftp_makereq(tftpfile);
268 
269           if (res) {
270                     dealloc(tftpfile, sizeof(*tftpfile));
271                     return res;
272           }
273           f->f_fsdata = (void *)tftpfile;
274           fsmod = "nfs";
275           return 0;
276 }
277 
278 __compactcall int
tftp_read(struct open_file * f,void * addr,size_t size,size_t * resid)279 tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
280 {
281           struct tftp_handle *tftpfile;
282 #if !defined(LIBSA_NO_TWIDDLE)
283           static int      tc = 0;
284 #endif
285           tftpfile = (struct tftp_handle *)f->f_fsdata;
286 
287           while (size > 0) {
288                     int needblock;
289                     size_t count;
290 
291                     needblock = tftpfile->off / SEGSIZE + 1;
292 
293                     if (tftpfile->currblock > needblock) {  /* seek backwards */
294 #ifndef TFTP_NOTERMINATE
295                               tftp_terminate(tftpfile);
296 #endif
297                               tftp_makereq(tftpfile);       /* no error check, it worked
298                                                        * for open */
299                     }
300 
301                     while (tftpfile->currblock < needblock) {
302                               int res;
303 
304 #if !defined(LIBSA_NO_TWIDDLE)
305                               if (!(tc++ % 16))
306                                         twiddle();
307 #endif
308 
309                               res = tftp_getnextblock(tftpfile);
310                               if (res) {          /* no answer */
311 #ifdef DEBUG
312                                         printf("%s: read error (block %d->%d)\n",
313                                             __func__, tftpfile->currblock, needblock);
314 #endif
315                                         return res;
316                               }
317                               if (tftpfile->islastblock)
318                                         break;
319                     }
320 
321                     if (tftpfile->currblock == needblock) {
322                               size_t offinblock, inbuffer;
323 
324                               offinblock = tftpfile->off % SEGSIZE;
325 
326                               if ((int)offinblock > tftpfile->validsize) {
327 #ifdef DEBUG
328                                         printf("%s: invalid offset %d\n", __func__,
329                                             tftpfile->off);
330 #endif
331                                         return EINVAL;
332                               }
333                               inbuffer = tftpfile->validsize - offinblock;
334                               count = (size < inbuffer ? size : inbuffer);
335                               (void)memcpy(addr,
336                                   tftpfile->lastdata.t.th_data + offinblock,
337                                   count);
338 
339                               addr = (char *)addr + count;
340                               tftpfile->off += count;
341                               size -= count;
342 
343                               if ((tftpfile->islastblock) && (count == inbuffer))
344                                         break;    /* EOF */
345                     } else {
346 #ifdef DEBUG
347                               printf("%s: block %d not found\n",
348                                   __func__, needblock);
349 #endif
350                               return EINVAL;
351                     }
352 
353           }
354 
355           if (resid)
356                     *resid = size;
357           return 0;
358 }
359 
360 __compactcall int
tftp_close(struct open_file * f)361 tftp_close(struct open_file *f)
362 {
363           struct tftp_handle *tftpfile;
364           tftpfile = (struct tftp_handle *)f->f_fsdata;
365 
366 #ifdef TFTP_NOTERMINATE
367           /* let it time out ... */
368 #else
369           tftp_terminate(tftpfile);
370 #endif
371 
372           dealloc(tftpfile, sizeof(*tftpfile));
373           return 0;
374 }
375 
376 __compactcall int
tftp_write(struct open_file * f,void * start,size_t size,size_t * resid)377 tftp_write(struct open_file *f, void *start, size_t size, size_t *resid)
378 {
379 
380           return EROFS;
381 }
382 
383 static ssize_t
tftp_size_of_file(struct tftp_handle * tftpfile)384 tftp_size_of_file(struct tftp_handle *tftpfile)
385 {
386           ssize_t filesize;
387 
388           if (tftpfile->currblock > 1) {          /* move to start of file */
389 #ifndef TFTP_NOTERMINATE
390                     tftp_terminate(tftpfile);
391 #endif
392                     tftp_makereq(tftpfile);       /* no error check, it worked
393                                                    * for open */
394           }
395 
396           /* start with the size of block 1 */
397           filesize = tftpfile->validsize;
398 
399           /* and keep adding the sizes till we hit the last block */
400           while (!tftpfile->islastblock) {
401                     int res;
402 
403                     res = tftp_getnextblock(tftpfile);
404                     if (res) {          /* no answer */
405 #ifdef DEBUG
406                               printf("%s: read error (block %d)\n",
407                                   __func__, tftpfile->currblock);
408 #endif
409                               return -1;
410                     }
411                     filesize += tftpfile->validsize;
412           }
413 #ifdef DEBUG
414           printf("%s: file is %zu bytes\n", __func__, filesize);
415 #endif
416           return filesize;
417 }
418 
419 __compactcall int
tftp_stat(struct open_file * f,struct stat * sb)420 tftp_stat(struct open_file *f, struct stat *sb)
421 {
422           struct tftp_handle *tftpfile;
423           tftpfile = (struct tftp_handle *)f->f_fsdata;
424 
425           sb->st_mode = 0444;
426           sb->st_nlink = 1;
427           sb->st_uid = 0;
428           sb->st_gid = 0;
429           sb->st_size = tftp_size_of_file(tftpfile);
430           return 0;
431 }
432 
433 #if defined(LIBSA_ENABLE_LS_OP)
434 #include "ls.h"
435 __compactcall void
tftp_ls(struct open_file * f,const char * pattern)436 tftp_ls(struct open_file *f, const char *pattern)
437 {
438           lsunsup("tftp");
439 }
440 #endif
441 
442 __compactcall off_t
tftp_seek(struct open_file * f,off_t offset,int where)443 tftp_seek(struct open_file *f, off_t offset, int where)
444 {
445           struct tftp_handle *tftpfile;
446           tftpfile = (struct tftp_handle *)f->f_fsdata;
447 
448           switch (where) {
449           case SEEK_SET:
450                     tftpfile->off = offset;
451                     break;
452           case SEEK_CUR:
453                     tftpfile->off += offset;
454                     break;
455           default:
456                     errno = EOFFSET;
457                     return -1;
458           }
459           return tftpfile->off;
460 }
461