/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2003 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */
/* All Rights Reserved */
/*
* University Copyright- Copyright (c) 1982, 1986, 1988
* The Regents of the University of California
* All Rights Reserved
*
* University Acknowledgment- Portions of this document are derived from
* software developed by the University of California, Berkeley, and its
* contributors.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Simple minded read-ahead/write-behind subroutines for tftp user and
* server. Written originally with multiple buffers in mind, but current
* implementation has two buffer logic wired in.
*
* Todo: add some sort of final error check so when the write-buffer
* is finally flushed, the caller can detect if the disk filled up
* (or had an i/o error) and return a nak to the other side.
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/filio.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <errno.h>
#include <poll.h>
#include <string.h>
#include "tftpcommon.h"
struct errmsg errmsgs[] = {
{ EUNDEF, "Undefined error code" },
{ ENOTFOUND, "File not found" },
{ EACCESS, "Access violation" },
{ ENOSPACE, "Disk full or allocation exceeded" },
{ EBADOP, "Illegal TFTP operation" },
{ EBADID, "Unknown transfer ID" },
{ EEXISTS, "File already exists" },
{ ENOUSER, "No such user" },
{ EOPTNEG, "Option negotiation error" },
{ -1, NULL }
};
static struct bf {
int counter; /* size of data in buffer, or flag */
tftpbuf buf; /* room for data packet */
} bfs[2];
extern int blocksize; /* Number of data bytes in a DATA packet */
/* Values for bf.counter */
#define BF_ALLOC -3 /* alloc'd but not yet filled */
#define BF_FREE -2 /* free */
/* [-1 .. blocksize] = size of data in the data buffer */
static int nextone; /* index of next buffer to use */
static int current; /* index of buffer in use */
/* control flags for crlf conversions */
static int newline = 0; /* fillbuf: in middle of newline expansion */
static int prevchar = -1; /* putbuf: previous char (cr check) */
static struct tftphdr *rw_init(int);
struct tftphdr *w_init() { return (rw_init(0)); } /* write-behind */
struct tftphdr *r_init() { return (rw_init(1)); } /* read-ahead */
/*
* Init for either read-ahead or write-behind.
* x is zero for write-behind, one for read-head.
*/
static struct tftphdr *
rw_init(int x)
{
newline = 0; /* init crlf flag */
prevchar = -1;
bfs[0].counter = BF_ALLOC; /* pass out the first buffer */
current = 0;
bfs[1].counter = BF_FREE;
nextone = x; /* ahead or behind? */
return (&bfs[0].buf.tb_hdr);
}
/*
* Have emptied current buffer by sending to net and getting ack.
* Free it and return next buffer filled with data.
*/
int
readit(FILE *file, struct tftphdr **dpp, int convert)
{
struct bf *b;
bfs[current].counter = BF_FREE; /* free old one */
current = !current; /* "incr" current */
b = &bfs[current]; /* look at new buffer */
if (b->counter == BF_FREE) /* if it's empty */
read_ahead(file, convert); /* fill it */
*dpp = &b->buf.tb_hdr; /* set caller's ptr */
return (b->counter);
}
/*
* fill the input buffer, doing ascii conversions if requested
* conversions are lf -> cr,lf and cr -> cr, nul
*/
void
read_ahead(FILE *file, int convert)
{
int i;
char *p;
int c;
struct bf *b;
struct tftphdr *dp;
b = &bfs[nextone]; /* look at "next" buffer */
if (b->counter != BF_FREE) /* nop if not free */
return;
nextone = !nextone; /* "incr" next buffer ptr */
dp = &b->buf.tb_hdr;
if (!convert) {
b->counter = fread(dp->th_data, sizeof (char), blocksize,
file);
if (ferror(file))
b->counter = -1;
return;
}
p = dp->th_data;
for (i = 0; i < blocksize; i++) {
if (newline) {
if (prevchar == '\n')
c = '\n'; /* lf to cr,lf */
else c = '\0'; /* cr to cr,nul */
newline = 0;
} else {
c = getc(file);
if (c == EOF) break;
if (c == '\n' || c == '\r') {
prevchar = c;
c = '\r';
newline = 1;
}
}
*p++ = c;
}
b->counter = (int)(p - dp->th_data);
}
/*
* Update count associated with the buffer, get new buffer
* from the queue. Calls write_behind only if next buffer not
* available.
*/
int
writeit(FILE *file, struct tftphdr **dpp, int ct, int convert)
{
bfs[current].counter = ct; /* set size of data to write */
current = !current; /* switch to other buffer */
if (bfs[current].counter != BF_FREE) /* if not free */
if (write_behind(file, convert) < 0) /* flush it */
ct = -1;
bfs[current].counter = BF_ALLOC; /* mark as alloc'd */
*dpp = &bfs[current].buf.tb_hdr;
return (ct); /* this is a lie of course */
}
/*
* Output a buffer to a file, converting from netascii if requested.
* CR,NUL -> CR and CR,LF => LF.
* Note spec is undefined if we get CR as last byte of file or a
* CR followed by anything else. In this case we leave it alone.
*/
int
write_behind(FILE *file, int convert)
{
char *buf;
int count;
int ct;
char *p;
int c; /* current character */
struct bf *b;
struct tftphdr *dp;
b = &bfs[nextone];
if (b->counter < -1) /* anything to flush? */
return (0); /* just nop if nothing to do */
count = b->counter; /* remember byte count */
b->counter = BF_FREE; /* reset flag */
dp = &b->buf.tb_hdr;
nextone = !nextone; /* incr for next time */
buf = dp->th_data;
if (count <= 0)
return (0); /* nak logic? */
if (!convert) {
size_t left = count;
while (left > 0) {
size_t written;
written = fwrite(buf, sizeof (char), left, file);
if (ferror(file)) {
/* Retry if we were interrupted by a signal. */
if (errno == EINTR)
continue;
return (-1);
}
if (written == 0)
return (-1);
left -= written;
buf += written;
}
return (count);
}
p = buf;
ct = count;
while (ct--) { /* loop over the buffer */
c = *p++; /* pick up a character */
if (prevchar == '\r') { /* if prev char was cr */
if (c == '\n') { /* if have cr,lf then just */
/* smash lf on top of the cr */
if (fseek(file, -1, SEEK_CUR) < 0)
return (-1);
} else {
if (c == '\0') {
/*
* If we have cr,nul then
* just skip over the putc.
*/
prevchar = 0;
continue;
}
}
/* else just fall through and allow it */
}
if (putc(c, file) == EOF)
return (-1);
prevchar = c;
}
return (count);
}
/*
* When an error has occurred, it is possible that the two sides
* are out of synch. Ie: that what I think is the other side's
* response to packet N is really their response to packet N-1.
*
* So, to try to prevent that, we flush all the input queued up
* for us on the network connection on our host.
*
* We return the number of packets we flushed (mostly for reporting
* when trace is active) or -1 in case of an error.
*/
int
synchnet(int socket)
{
struct pollfd pfd;
int packets;
pfd.fd = socket;
pfd.events = POLLRDNORM;
for (packets = 0; ; packets++) {
char buf;
struct sockaddr_in6 from;
socklen_t fromlen;
if (poll(&pfd, 1, 0) <= 0)
break;
/*
* A one byte buffer is enough because recvfrom() will
* discard the remaining data of the packet.
*/
fromlen = sizeof (from);
if (recvfrom(socket, &buf, sizeof (buf), 0,
(struct sockaddr *)&from, &fromlen) < 0)
return (-1);
}
return (packets);
}
/*
* Return a pointer to the next field in string s, or return NULL if no
* terminating NUL is found for the current field before end.
*/
char *
next_field(const char *s, const char *end)
{
if (s < end) {
s = memchr(s, 0, end - s);
if (s != NULL)
return ((char *)s + 1);
}
return (NULL);
}
/*
* Print to stream options in the format option_name=option_value
*/
void
print_options(FILE *stream, char *opts, int len)
{
char *cp, *optname, *optval;
char *endopts = opts + len;
int first = 1;
/*
* Ignore null padding, appended by broken TFTP clients to
* requests which don't include options.
*/
cp = opts;
while ((cp < endopts) && (*cp == '\0'))
cp++;
if (cp == endopts)
return;
while (opts < endopts) {
optname = opts;
if ((optval = next_field(optname, endopts)) == NULL) {
(void) putc('?', stream);
return;
}
if (first)
first = 0;
else
(void) putc(' ', stream);
(void) fputs(optname, stream);
if ((opts = next_field(optval, endopts)) == NULL) {
(void) putc('?', stream);
return;
}
(void) fprintf(stream, "=%s", optval);
}
}
/*
* Turn off the alarm timer and ensure any pending SIGALRM signal is ignored.
*/
void
cancel_alarm(void)
{
(void) alarm(0);
(void) signal(SIGALRM, SIG_IGN);
(void) sigrelse(SIGALRM);
}