tftp.c revision 1.26
1/* $NetBSD: tftp.c,v 1.26 2008/05/11 11:29:12 chris 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[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 __P((struct iodesc *, void *, size_t, time_t)); 94static int tftp_makereq __P((struct tftp_handle *)); 95static int tftp_getnextblock __P((struct tftp_handle *)); 96#ifndef TFTP_NOTERMINATE 97static void tftp_terminate __P((struct tftp_handle *)); 98#endif 99static ssize_t tftp_size_of_file __P((struct tftp_handle *tftpfile)); 100 101static ssize_t 102recvtftp(struct iodesc *d, void *pkt, size_t len, time_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[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[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[HEADER_SIZE]; 233 struct tftphdr t; 234 } wbuf; 235 char *wtail; 236 237 if (h->islastblock) { 238 wbuf.t.th_opcode = htons((u_short)ACK); 239 wbuf.t.th_block = htons((u_short)h->currblock); 240 } else { 241 wbuf.t.th_opcode = htons((u_short)ERROR); 242 wbuf.t.th_code = htons((u_short)ENOSPACE); /* ??? */ 243 } 244 wtail = (char *)&wbuf.t.th_data; 245 246 (void)sendudp(h->iodesc, &wbuf.t, wtail - (char *)&wbuf.t); 247} 248#endif 249 250int 251tftp_open(const char *path, struct open_file *f) 252{ 253 struct tftp_handle *tftpfile; 254 struct iodesc *io; 255 int res; 256 257 tftpfile = (struct tftp_handle *)alloc(sizeof(*tftpfile)); 258 if (!tftpfile) 259 return ENOMEM; 260 261 tftpfile->iodesc = io = socktodesc(*(int *)(f->f_devdata)); 262 io->destip = servip; 263 tftpfile->off = 0; 264 tftpfile->path = path; /* XXXXXXX we hope it's static */ 265 266 res = tftp_makereq(tftpfile); 267 268 if (res) { 269 dealloc(tftpfile, sizeof(*tftpfile)); 270 return res; 271 } 272 f->f_fsdata = (void *)tftpfile; 273 return 0; 274} 275 276int 277tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid) 278{ 279 struct tftp_handle *tftpfile; 280#if !defined(LIBSA_NO_TWIDDLE) 281 static int tc = 0; 282#endif 283 tftpfile = (struct tftp_handle *)f->f_fsdata; 284 285 while (size > 0) { 286 int needblock; 287 size_t count; 288 289#if !defined(LIBSA_NO_TWIDDLE) 290 if (!(tc++ % 16)) 291 twiddle(); 292#endif 293 294 needblock = tftpfile->off / SEGSIZE + 1; 295 296 if (tftpfile->currblock > needblock) { /* seek backwards */ 297#ifndef TFTP_NOTERMINATE 298 tftp_terminate(tftpfile); 299#endif 300 tftp_makereq(tftpfile); /* no error check, it worked 301 * for open */ 302 } 303 304 while (tftpfile->currblock < needblock) { 305 int res; 306 307 res = tftp_getnextblock(tftpfile); 308 if (res) { /* no answer */ 309#ifdef DEBUG 310 printf("tftp: read error (block %d->%d)\n", 311 tftpfile->currblock, needblock); 312#endif 313 return res; 314 } 315 if (tftpfile->islastblock) 316 break; 317 } 318 319 if (tftpfile->currblock == needblock) { 320 size_t offinblock, inbuffer; 321 322 offinblock = tftpfile->off % SEGSIZE; 323 324 inbuffer = tftpfile->validsize - offinblock; 325 if (inbuffer < 0) { 326#ifdef DEBUG 327 printf("tftp: invalid offset %d\n", 328 tftpfile->off); 329#endif 330 return EINVAL; 331 } 332 count = (size < inbuffer ? size : inbuffer); 333 (void)memcpy(addr, 334 tftpfile->lastdata.t.th_data + offinblock, 335 count); 336 337 addr = (char *)addr + count; 338 tftpfile->off += count; 339 size -= count; 340 341 if ((tftpfile->islastblock) && (count == inbuffer)) 342 break; /* EOF */ 343 } else { 344#ifdef DEBUG 345 printf("tftp: block %d not found\n", needblock); 346#endif 347 return EINVAL; 348 } 349 350 } 351 352 if (resid) 353 *resid = size; 354 return 0; 355} 356 357int 358tftp_close(struct open_file *f) 359{ 360 struct tftp_handle *tftpfile; 361 tftpfile = (struct tftp_handle *)f->f_fsdata; 362 363#ifdef TFTP_NOTERMINATE 364 /* let it time out ... */ 365#else 366 tftp_terminate(tftpfile); 367#endif 368 369 dealloc(tftpfile, sizeof(*tftpfile)); 370 return 0; 371} 372 373int 374tftp_write(struct open_file *f, void *start, size_t size, size_t *resid) 375{ 376 377 return EROFS; 378} 379 380static ssize_t 381tftp_size_of_file(struct tftp_handle *tftpfile) 382{ 383 ssize_t filesize; 384 385 if (tftpfile->currblock > 1) { /* move to start of file */ 386#ifndef TFTP_NOTERMINATE 387 tftp_terminate(tftpfile); 388#endif 389 tftp_makereq(tftpfile); /* no error check, it worked 390 * for open */ 391 } 392 393 /* start with the size of block 1 */ 394 filesize = tftpfile->validsize; 395 396 /* and keep adding the sizes till we hit the last block */ 397 while (!tftpfile->islastblock) { 398 int res; 399 400 res = tftp_getnextblock(tftpfile); 401 if (res) { /* no answer */ 402#ifdef DEBUG 403 printf("tftp: read error (block %d)\n", 404 tftpfile->currblock); 405#endif 406 return -1; 407 } 408 filesize += tftpfile->validsize; 409 } 410#ifdef DEBUG 411 printf("tftp_size_of_file: file is %d bytes\n", filesize); 412#endif 413 return filesize; 414} 415 416int 417tftp_stat(struct open_file *f, struct stat *sb) 418{ 419 struct tftp_handle *tftpfile; 420 tftpfile = (struct tftp_handle *)f->f_fsdata; 421 422 sb->st_mode = 0444; 423 sb->st_nlink = 1; 424 sb->st_uid = 0; 425 sb->st_gid = 0; 426 sb->st_size = tftp_size_of_file(tftpfile); 427 return 0; 428} 429 430off_t 431tftp_seek(struct open_file *f, off_t offset, int where) 432{ 433 struct tftp_handle *tftpfile; 434 tftpfile = (struct tftp_handle *)f->f_fsdata; 435 436 switch (where) { 437 case SEEK_SET: 438 tftpfile->off = offset; 439 break; 440 case SEEK_CUR: 441 tftpfile->off += offset; 442 break; 443 default: 444 errno = EOFFSET; 445 return -1; 446 } 447 return tftpfile->off; 448} 449