1/* $NetBSD: tftp.c,v 1.33 2011/07/30 03:43:20 jakllsch 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 62extern struct in_addr servip; 63 64static int tftpport = 2000; 65 66#define RSPACE 520 /* max data packet, rounded up */ 67 68struct 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 82static const int tftperrors[8] = { 83 0, /* ??? */ 84 ENOENT, 85 EPERM, 86 ENOSPC, 87 EINVAL, /* ??? */ 88 EINVAL, /* ??? */ 89 EEXIST, 90 EINVAL, /* ??? */ 91}; 92 93static ssize_t recvtftp(struct iodesc *, void *, size_t, saseconds_t); 94static int tftp_makereq(struct tftp_handle *); 95static int tftp_getnextblock(struct tftp_handle *); 96#ifndef TFTP_NOTERMINATE 97static void tftp_terminate(struct tftp_handle *); 98#endif 99static ssize_t tftp_size_of_file(struct tftp_handle *tftpfile); 100 101static ssize_t 102recvtftp(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 (htons(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) */ 152static int 153tftp_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 */ 195static int 196tftp_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 228static void 229tftp_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 252tftp_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 279tftp_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#if !defined(LIBSA_NO_TWIDDLE) 292 if (!(tc++ % 16)) 293 twiddle(); 294#endif 295 296 needblock = tftpfile->off / SEGSIZE + 1; 297 298 if (tftpfile->currblock > needblock) { /* seek backwards */ 299#ifndef TFTP_NOTERMINATE 300 tftp_terminate(tftpfile); 301#endif 302 tftp_makereq(tftpfile); /* no error check, it worked 303 * for open */ 304 } 305 306 while (tftpfile->currblock < needblock) { 307 int res; 308 309 res = tftp_getnextblock(tftpfile); 310 if (res) { /* no answer */ 311#ifdef DEBUG 312 printf("tftp: read error (block %d->%d)\n", 313 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 (offinblock > tftpfile->validsize) { 327#ifdef DEBUG 328 printf("tftp: invalid offset %d\n", 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("tftp: block %d not found\n", needblock); 348#endif 349 return EINVAL; 350 } 351 352 } 353 354 if (resid) 355 *resid = size; 356 return 0; 357} 358 359__compactcall int 360tftp_close(struct open_file *f) 361{ 362 struct tftp_handle *tftpfile; 363 tftpfile = (struct tftp_handle *)f->f_fsdata; 364 365#ifdef TFTP_NOTERMINATE 366 /* let it time out ... */ 367#else 368 tftp_terminate(tftpfile); 369#endif 370 371 dealloc(tftpfile, sizeof(*tftpfile)); 372 return 0; 373} 374 375__compactcall int 376tftp_write(struct open_file *f, void *start, size_t size, size_t *resid) 377{ 378 379 return EROFS; 380} 381 382static ssize_t 383tftp_size_of_file(struct tftp_handle *tftpfile) 384{ 385 ssize_t filesize; 386 387 if (tftpfile->currblock > 1) { /* move to start of file */ 388#ifndef TFTP_NOTERMINATE 389 tftp_terminate(tftpfile); 390#endif 391 tftp_makereq(tftpfile); /* no error check, it worked 392 * for open */ 393 } 394 395 /* start with the size of block 1 */ 396 filesize = tftpfile->validsize; 397 398 /* and keep adding the sizes till we hit the last block */ 399 while (!tftpfile->islastblock) { 400 int res; 401 402 res = tftp_getnextblock(tftpfile); 403 if (res) { /* no answer */ 404#ifdef DEBUG 405 printf("tftp: read error (block %d)\n", 406 tftpfile->currblock); 407#endif 408 return -1; 409 } 410 filesize += tftpfile->validsize; 411 } 412#ifdef DEBUG 413 printf("tftp_size_of_file: file is %zu bytes\n", filesize); 414#endif 415 return filesize; 416} 417 418__compactcall int 419tftp_stat(struct open_file *f, struct stat *sb) 420{ 421 struct tftp_handle *tftpfile; 422 tftpfile = (struct tftp_handle *)f->f_fsdata; 423 424 sb->st_mode = 0444; 425 sb->st_nlink = 1; 426 sb->st_uid = 0; 427 sb->st_gid = 0; 428 sb->st_size = tftp_size_of_file(tftpfile); 429 return 0; 430} 431 432#if defined(LIBSA_ENABLE_LS_OP) 433__compactcall void 434tftp_ls(struct open_file *f, const char *pattern) 435{ 436 printf("Currently ls command is unsupported by tftp\n"); 437 return; 438} 439#endif 440 441__compactcall off_t 442tftp_seek(struct open_file *f, off_t offset, int where) 443{ 444 struct tftp_handle *tftpfile; 445 tftpfile = (struct tftp_handle *)f->f_fsdata; 446 447 switch (where) { 448 case SEEK_SET: 449 tftpfile->off = offset; 450 break; 451 case SEEK_CUR: 452 tftpfile->off += offset; 453 break; 454 default: 455 errno = EOFFSET; 456 return -1; 457 } 458 return tftpfile->off; 459} 460