diff options
author | 2004-03-19 13:48:18 +0000 | |
---|---|---|
committer | 2004-03-19 13:48:18 +0000 | |
commit | ef7aef7b79739a800b798b4976d7318353cc3a78 (patch) | |
tree | 4f94b0def738a5782c8b313bdb5215ede577bccd /sys/lib/libsa/tftp.c | |
parent | Fix memory leak in error path. Found by Patrick Latifi. OK henning@ (diff) | |
download | wireguard-openbsd-ef7aef7b79739a800b798b4976d7318353cc3a78.tar.xz wireguard-openbsd-ef7aef7b79739a800b798b4976d7318353cc3a78.zip |
Enter pxeboot, derived from the NetBSD implementation. Initially
intended to support network installs using bsd.rd over TFTP.
Thanks to the many who tested, including Diana Eichert.
ok deraadt@
Diffstat (limited to 'sys/lib/libsa/tftp.c')
-rw-r--r-- | sys/lib/libsa/tftp.c | 434 |
1 files changed, 434 insertions, 0 deletions
diff --git a/sys/lib/libsa/tftp.c b/sys/lib/libsa/tftp.c new file mode 100644 index 00000000000..c5c8ccc4bba --- /dev/null +++ b/sys/lib/libsa/tftp.c @@ -0,0 +1,434 @@ +/* $OpenBSD: tftp.c,v 1.1 2004/03/19 13:48:20 tom Exp $ */ +/* $NetBSD: tftp.c,v 1.15 2003/08/18 15:45:29 dsl Exp $ */ + +/* + * Copyright (c) 1996 + * Matthias Drochner. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed for the NetBSD Project + * by Matthias Drochner. + * 4. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Simple TFTP implementation for libsa. + * Assumes: + * - socket descriptor (int) at open_file->f_devdata + * - server host IP in global servip + * Restrictions: + * - read only + * - lseek only with SEEK_SET or SEEK_CUR + * - no big time differences between transfers (<tftp timeout) + */ + +/* + * XXX Does not currently implement: + * XXX + * XXX LIBSA_NO_FS_CLOSE + * XXX LIBSA_NO_FS_SEEK + * XXX LIBSA_NO_FS_WRITE + * XXX LIBSA_NO_FS_SYMLINK (does this even make sense?) + * XXX LIBSA_FS_SINGLECOMPONENT (does this even make sense?) + */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <netinet/in.h> +#include <netinet/udp.h> +#include <netinet/in_systm.h> +#include <lib/libkern/libkern.h> + +#include "stand.h" +#include "net.h" +#include "netif.h" + +#include "tftp.h" + +extern struct in_addr servip; + +static int tftpport = 2000; + +#define RSPACE 520 /* max data packet, rounded up */ + +struct tftp_handle { + struct iodesc *iodesc; + int currblock; /* contents of lastdata */ + int islastblock; /* flag */ + int validsize; + int off; + const char *path; /* saved for re-requests */ + struct { + u_char header[HEADER_SIZE]; + struct tftphdr t; + u_char space[RSPACE]; + } lastdata; +}; + +static const int tftperrors[8] = { + 0, /* ??? */ + ENOENT, + EPERM, + ENOSPC, + EINVAL, /* ??? */ + EINVAL, /* ??? */ + EEXIST, + EINVAL /* ??? */ +}; + +ssize_t recvtftp(struct iodesc *, void *, size_t, time_t); +int tftp_makereq(struct tftp_handle *); +int tftp_getnextblock(struct tftp_handle *); +#ifndef TFTP_NOTERMINATE +void tftp_terminate(struct tftp_handle *); +#endif + +ssize_t +recvtftp(struct iodesc *d, void *pkt, size_t len, time_t tleft) +{ + ssize_t n; + struct tftphdr *t; + + errno = 0; + + n = readudp(d, pkt, len, tleft); + + if (n < 4) + return -1; + + t = (struct tftphdr *) pkt; + switch (ntohs(t->th_opcode)) { + case DATA: + if (htons(t->th_block) != d->xid) { + /* + * Expected block? + */ + return -1; + } + if (d->xid == 1) { + /* + * First data packet from new port. + */ + struct udphdr *uh; + uh = (struct udphdr *) pkt - 1; + d->destport = uh->uh_sport; + } /* else check uh_sport has not changed??? */ + return (n - (t->th_data - (char *)t)); + case ERROR: + if ((unsigned) ntohs(t->th_code) >= 8) { + printf("illegal tftp error %d\n", ntohs(t->th_code)); + errno = EIO; + } else { +#ifdef DEBUG + printf("tftp-error %d\n", ntohs(t->th_code)); +#endif + errno = tftperrors[ntohs(t->th_code)]; + } + return -1; + default: +#ifdef DEBUG + printf("tftp type %d not handled\n", ntohs(t->th_opcode)); +#endif + return -1; + } +} + +/* send request, expect first block (or error) */ +int +tftp_makereq(struct tftp_handle *h) +{ + struct { + u_char header[HEADER_SIZE]; + struct tftphdr t; + u_char space[FNAME_SIZE + 6]; + } wbuf; + char *wtail; + int l; + ssize_t res; + struct tftphdr *t; + + bzero(&wbuf, sizeof(wbuf)); + + wbuf.t.th_opcode = htons((u_short) RRQ); + wtail = wbuf.t.th_stuff; + l = strlen(h->path); + bcopy(h->path, wtail, l + 1); + wtail += l + 1; + bcopy("octet", wtail, 6); + wtail += 6; + + t = &h->lastdata.t; + + /* h->iodesc->myport = htons(--tftpport); */ + h->iodesc->myport = htons(tftpport + (getsecs() & 0x3ff)); + h->iodesc->destport = htons(IPPORT_TFTP); + h->iodesc->xid = 1; /* expected block */ + + res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *) &wbuf.t, + recvtftp, t, sizeof(*t) + RSPACE); + + if (res == -1) + return errno; + + h->currblock = 1; + h->validsize = res; + h->islastblock = 0; + if (res < SEGSIZE) + h->islastblock = 1; /* very short file */ + return 0; +} + +/* ack block, expect next */ +int +tftp_getnextblock(struct tftp_handle *h) +{ + struct { + u_char header[HEADER_SIZE]; + struct tftphdr t; + } wbuf; + char *wtail; + int res; + struct tftphdr *t; + + bzero(&wbuf, sizeof(wbuf)); + + wbuf.t.th_opcode = htons((u_short) ACK); + wbuf.t.th_block = htons((u_short) h->currblock); + wtail = (char *) &wbuf.t.th_data; + + t = &h->lastdata.t; + + h->iodesc->xid = h->currblock + 1; /* expected block */ + + res = sendrecv(h->iodesc, sendudp, &wbuf.t, wtail - (char *) &wbuf.t, + recvtftp, t, sizeof(*t) + RSPACE); + + if (res == -1) /* 0 is OK! */ + return errno; + + h->currblock++; + h->validsize = res; + if (res < SEGSIZE) + h->islastblock = 1; /* EOF */ + return 0; +} + +#ifndef TFTP_NOTERMINATE +void +tftp_terminate(struct tftp_handle *h) +{ + struct { + u_char header[HEADER_SIZE]; + struct tftphdr t; + } wbuf; + char *wtail; + + bzero(&wbuf, sizeof(wbuf)); + + if (h->islastblock) { + wbuf.t.th_opcode = htons((u_short) ACK); + wbuf.t.th_block = htons((u_short) h->currblock); + } else { + wbuf.t.th_opcode = htons((u_short) ERROR); + wbuf.t.th_code = htons((u_short) ENOSPACE); /* ??? */ + } + wtail = (char *) &wbuf.t.th_data; + + (void) sendudp(h->iodesc, &wbuf.t, wtail - (char *) &wbuf.t); +} +#endif + +int +tftp_open(char *path, struct open_file *f) +{ + struct tftp_handle *tftpfile; + struct iodesc *io; + int res; + + tftpfile = (struct tftp_handle *) alloc(sizeof(*tftpfile)); + if (tftpfile == NULL) + return ENOMEM; + + tftpfile->iodesc = io = socktodesc(*(int *) (f->f_devdata)); + io->destip = servip; + tftpfile->off = 0; + tftpfile->path = path; /* XXXXXXX we hope it's static */ + + res = tftp_makereq(tftpfile); + + if (res) { + free(tftpfile, sizeof(*tftpfile)); + return res; + } + f->f_fsdata = (void *) tftpfile; + return 0; +} + +int +tftp_read(struct open_file *f, void *addr, size_t size, size_t *resid) +{ + struct tftp_handle *tftpfile; +#if !defined(LIBSA_NO_TWIDDLE) + static int tc = 0; +#endif + tftpfile = (struct tftp_handle *) f->f_fsdata; + + while (size > 0) { + int needblock; + size_t count; + + needblock = tftpfile->off / SEGSIZE + 1; + + if (tftpfile->currblock > needblock) { /* seek backwards */ +#ifndef TFTP_NOTERMINATE + tftp_terminate(tftpfile); +#endif + /* Don't bother to check retval: it worked for open() */ + tftp_makereq(tftpfile); + } + + while (tftpfile->currblock < needblock) { + int res; + +#if !defined(LIBSA_NO_TWIDDLE) + if ((tc++ % 16) == 0) + twiddle(); +#endif + res = tftp_getnextblock(tftpfile); + if (res) { /* no answer */ +#ifdef DEBUG + printf("tftp: read error (block %d->%d)\n", + tftpfile->currblock, needblock); +#endif + return res; + } + if (tftpfile->islastblock) + break; + } + + if (tftpfile->currblock == needblock) { + size_t offinblock, inbuffer; + + offinblock = tftpfile->off % SEGSIZE; + + inbuffer = tftpfile->validsize - offinblock; + if (inbuffer < 0) { +#ifdef DEBUG + printf("tftp: invalid offset %d\n", + tftpfile->off); +#endif + return EINVAL; + } + count = (size < inbuffer ? size : inbuffer); + bcopy(tftpfile->lastdata.t.th_data + offinblock, + addr, count); + + addr = (caddr_t)addr + count; + tftpfile->off += count; + size -= count; + + if ((tftpfile->islastblock) && (count == inbuffer)) + break; /* EOF */ + } else { +#ifdef DEBUG + printf("tftp: block %d not found\n", needblock); +#endif + return EINVAL; + } + + } + + if (resid != NULL) + *resid = size; + return 0; +} + +int +tftp_close(struct open_file *f) +{ + struct tftp_handle *tftpfile; + tftpfile = (struct tftp_handle *) f->f_fsdata; + +#ifdef TFTP_NOTERMINATE + /* let it time out ... */ +#else + tftp_terminate(tftpfile); +#endif + + free(tftpfile, sizeof(*tftpfile)); + return 0; +} + +int +tftp_write(struct open_file *f, void *start, size_t size, size_t *resid) +{ + return EROFS; +} + +int +tftp_stat(struct open_file *f, struct stat *sb) +{ + struct tftp_handle *tftpfile; + tftpfile = (struct tftp_handle *) f->f_fsdata; + + sb->st_mode = 0444; + sb->st_nlink = 1; + sb->st_uid = 0; + sb->st_gid = 0; + sb->st_size = -1; + + return 0; +} + +off_t +tftp_seek(struct open_file *f, off_t offset, int where) +{ + struct tftp_handle *tftpfile; + tftpfile = (struct tftp_handle *) f->f_fsdata; + + switch (where) { + case SEEK_SET: + tftpfile->off = offset; + break; + case SEEK_CUR: + tftpfile->off += offset; + break; + default: + errno = EOFFSET; + return -1; + } + + return (tftpfile->off); +} + +/* + * Not implemented. + */ +#ifndef NO_READDIR +int +tftp_readdir(struct open_file *f, char *name) +{ + return EROFS; +} +#endif |