tftp.c revision 1.19
1/* $NetBSD: tftp.c,v 1.19 2005/12/11 12:24:46 christos 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 99 100static ssize_t 101recvtftp(d, pkt, len, tleft) 102 struct iodesc *d; 103 void *pkt; 104 size_t len; 105 time_t tleft; 106{ 107 ssize_t n; 108 struct tftphdr *t; 109 110 errno = 0; 111 112 n = readudp(d, pkt, len, tleft); 113 114 if (n < 4) 115 return (-1); 116 117 t = (struct tftphdr *) pkt; 118 switch (ntohs(t->th_opcode)) { 119 case DATA: 120 if (htons(t->th_block) != d->xid) { 121 /* 122 * Expected block? 123 */ 124 return (-1); 125 } 126 if (d->xid == 1) { 127 /* 128 * First data packet from new port. 129 */ 130 struct udphdr *uh; 131 uh = (struct udphdr *) pkt - 1; 132 d->destport = uh->uh_sport; 133 } /* else check uh_sport has not changed??? */ 134 return (n - (t->th_data - (char *)t)); 135 case ERROR: 136 if ((unsigned) ntohs(t->th_code) >= 8) { 137 printf("illegal tftp error %d\n", ntohs(t->th_code)); 138 errno = EIO; 139 } else { 140#ifdef DEBUG 141 printf("tftp-error %d\n", ntohs(t->th_code)); 142#endif 143 errno = tftperrors[ntohs(t->th_code)]; 144 } 145 return (-1); 146 default: 147#ifdef DEBUG 148 printf("tftp type %d not handled\n", ntohs(t->th_opcode)); 149#endif 150 return (-1); 151 } 152} 153 154/* send request, expect first block (or error) */ 155static int 156tftp_makereq(h) 157 struct tftp_handle *h; 158{ 159 struct { 160 u_char header[HEADER_SIZE]; 161 struct tftphdr t; 162 u_char space[FNAME_SIZE + 6]; 163 } wbuf; 164 char *wtail; 165 int l; 166 ssize_t res; 167 struct tftphdr *t; 168 169 wbuf.t.th_opcode = htons((u_short) RRQ); 170 wtail = wbuf.t.th_stuff; 171 l = strlen(h->path); 172 bcopy(h->path, wtail, l + 1); 173 wtail += l + 1; 174 bcopy("octet", wtail, 6); 175 wtail += 6; 176 177 t = &h->lastdata.t; 178 179 /* h->iodesc->myport = htons(--tftpport); */ 180 h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff)); 181 h->iodesc->destport = htons(IPPORT_TFTP); 182 h->iodesc->xid = 1; /* expected block */ 183 184 res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *) &wbuf.t, 185 recvtftp, t, sizeof(*t) + RSPACE); 186 187 if (res == -1) 188 return (errno); 189 190 h->currblock = 1; 191 h->validsize = res; 192 h->islastblock = 0; 193 if (res < SEGSIZE) 194 h->islastblock = 1; /* very short file */ 195 return (0); 196} 197 198/* ack block, expect next */ 199static int 200tftp_getnextblock(h) 201 struct tftp_handle *h; 202{ 203 struct { 204 u_char header[HEADER_SIZE]; 205 struct tftphdr t; 206 } wbuf; 207 char *wtail; 208 int res; 209 struct tftphdr *t; 210 211 wbuf.t.th_opcode = htons((u_short) ACK); 212 wbuf.t.th_block = htons((u_short) h->currblock); 213 wtail = (char *) &wbuf.t.th_data; 214 215 t = &h->lastdata.t; 216 217 h->iodesc->xid = h->currblock + 1; /* expected block */ 218 219 res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *) &wbuf.t, 220 recvtftp, t, sizeof(*t) + RSPACE); 221 222 if (res == -1) /* 0 is OK! */ 223 return (errno); 224 225 h->currblock++; 226 h->validsize = res; 227 if (res < SEGSIZE) 228 h->islastblock = 1; /* EOF */ 229 return (0); 230} 231 232#ifndef TFTP_NOTERMINATE 233static void 234tftp_terminate(h) 235 struct tftp_handle *h; 236{ 237 struct { 238 u_char header[HEADER_SIZE]; 239 struct tftphdr t; 240 } wbuf; 241 char *wtail; 242 243 if (h->islastblock) { 244 wbuf.t.th_opcode = htons((u_short) ACK); 245 wbuf.t.th_block = htons((u_short) h->currblock); 246 } else { 247 wbuf.t.th_opcode = htons((u_short) ERROR); 248 wbuf.t.th_code = htons((u_short) ENOSPACE); /* ??? */ 249 } 250 wtail = (char *) &wbuf.t.th_data; 251 252 (void) sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t); 253} 254#endif 255 256int 257tftp_open(path, f) 258 const char *path; 259 struct open_file *f; 260{ 261 struct tftp_handle *tftpfile; 262 struct iodesc *io; 263 int res; 264 265 tftpfile = (struct tftp_handle *) alloc(sizeof(*tftpfile)); 266 if (!tftpfile) 267 return (ENOMEM); 268 269 tftpfile->iodesc = io = socktodesc(*(int *) (f->f_devdata)); 270 io->destip = servip; 271 tftpfile->off = 0; 272 tftpfile->path = path; /* XXXXXXX we hope it's static */ 273 274 res = tftp_makereq(tftpfile); 275 276 if (res) { 277 free(tftpfile, sizeof(*tftpfile)); 278 return (res); 279 } 280 f->f_fsdata = (void *) tftpfile; 281 return (0); 282} 283 284int 285tftp_read(f, addr, size, resid) 286 struct open_file *f; 287 void *addr; 288 size_t size; 289 size_t *resid; /* out */ 290{ 291 struct tftp_handle *tftpfile; 292#if !defined(LIBSA_NO_TWIDDLE) 293 static int tc = 0; 294#endif 295 tftpfile = (struct tftp_handle *) f->f_fsdata; 296 297 while (size > 0) { 298 int needblock; 299 size_t count; 300 301#if !defined(LIBSA_NO_TWIDDLE) 302 if (!(tc++ % 16)) 303 twiddle(); 304#endif 305 306 needblock = tftpfile->off / SEGSIZE + 1; 307 308 if (tftpfile->currblock > needblock) { /* seek backwards */ 309#ifndef TFTP_NOTERMINATE 310 tftp_terminate(tftpfile); 311#endif 312 tftp_makereq(tftpfile); /* no error check, it worked 313 * for open */ 314 } 315 316 while (tftpfile->currblock < needblock) { 317 int res; 318 319 res = tftp_getnextblock(tftpfile); 320 if (res) { /* no answer */ 321#ifdef DEBUG 322 printf("tftp: read error (block %d->%d)\n", 323 tftpfile->currblock, needblock); 324#endif 325 return (res); 326 } 327 if (tftpfile->islastblock) 328 break; 329 } 330 331 if (tftpfile->currblock == needblock) { 332 size_t offinblock, inbuffer; 333 334 offinblock = tftpfile->off % SEGSIZE; 335 336 inbuffer = tftpfile->validsize - offinblock; 337 if (inbuffer < 0) { 338#ifdef DEBUG 339 printf("tftp: invalid offset %d\n", 340 tftpfile->off); 341#endif 342 return (EINVAL); 343 } 344 count = (size < inbuffer ? size : inbuffer); 345 bcopy(tftpfile->lastdata.t.th_data + offinblock, 346 addr, count); 347 348 addr = (caddr_t)addr + count; 349 tftpfile->off += count; 350 size -= count; 351 352 if ((tftpfile->islastblock) && (count == inbuffer)) 353 break; /* EOF */ 354 } else { 355#ifdef DEBUG 356 printf("tftp: block %d not found\n", needblock); 357#endif 358 return (EINVAL); 359 } 360 361 } 362 363 if (resid) 364 *resid = size; 365 return (0); 366} 367 368int 369tftp_close(f) 370 struct open_file *f; 371{ 372 struct tftp_handle *tftpfile; 373 tftpfile = (struct tftp_handle *) f->f_fsdata; 374 375#ifdef TFTP_NOTERMINATE 376 /* let it time out ... */ 377#else 378 tftp_terminate(tftpfile); 379#endif 380 381 free(tftpfile, sizeof(*tftpfile)); 382 return (0); 383} 384 385int 386tftp_write(f, start, size, resid) 387 struct open_file *f; 388 void *start; 389 size_t size; 390 size_t *resid; /* out */ 391{ 392 return (EROFS); 393} 394 395int 396tftp_stat(f, sb) 397 struct open_file *f; 398 struct stat *sb; 399{ 400 struct tftp_handle *tftpfile; 401 tftpfile = (struct tftp_handle *) f->f_fsdata; 402 403 sb->st_mode = 0444; 404 sb->st_nlink = 1; 405 sb->st_uid = 0; 406 sb->st_gid = 0; 407 sb->st_size = -1; 408 return (0); 409} 410 411off_t 412tftp_seek(f, offset, where) 413 struct open_file *f; 414 off_t offset; 415 int where; 416{ 417 struct tftp_handle *tftpfile; 418 tftpfile = (struct tftp_handle *) f->f_fsdata; 419 420 switch (where) { 421 case SEEK_SET: 422 tftpfile->off = offset; 423 break; 424 case SEEK_CUR: 425 tftpfile->off += offset; 426 break; 427 default: 428 errno = EOFFSET; 429 return (-1); 430 } 431 return (tftpfile->off); 432} 433