Go ahead and merge the work edwin@ on tftpd into the tree. It is a

lot better than what's in the tree now.  Edwin tested it at a prior
employer, but can't test it today.  I've found that it works a lot
better with the various uboot versions that I've used in my embedded
work.  Here's the pkg-descr from the port that describes the changes:

It all started when we got some new routers, which told me the
following when trying to upload configuration or download images
from it: The TFTP server doesn't support the blocksize option.

My curiousity was triggered, it took me some reading of RFCs and
other documentation to find out what was possible and what could
be done. Was plain TFTP very simple in its handshake, TFTP with
options was kind of messy because of its backwards capability: The
first packet returned could either be an acknowledgement of options,
or the first data packet.

Going through the source code of src/libexec/tftpd and going through
the code of src/usr.bin/tftp showed that there was a lot of duplicate
code, and the addition of options would only increase the amount
of duplicate code. After all, both the client and the server can
act as a sender and receiver.

At the end, it ended up with a nearly complete rewrite of the tftp
client and server. It has been tested against the following TFTP
clients and servers:

- Itself (yay!)
- The standard FreeBSD tftp client and server
- The Fedora Core 6 tftp client and server
- Cisco router tftp client
- Extreme Networks tftp client

It supports the following RFCs:

RFC1350 - THE TFTP PROTOCOL (REVISION 2)
RFC2347 - TFTP Option Extension
RFC2348 - TFTP Blocksize Option
RFC2349 - TFTP Timeout Interval and Transfer Size Options
RFC3617 - Uniform Resource Identifier (URI) Scheme and Applicability
          Statement for the Trivial File Transfer Protocol (TFTP)

It supports the following unofficial TFTP Options as described at
http://www.compuphase.com/tftp.htm:

blksize2 - Block size restricted to powers of 2, excluding protocol headers
rollover - Block counter roll-over (roll back to zero or to one)

From the tftp program point of view the following things are changed:

- New commands: "blocksize", "blocksize2", "rollover" and "options"
- Development features: "debug" and "packetdrop"

If you try this tftp/tftpd implementation, please let me know if
it works (or doesn't work) and against which implementaion so I can
get a list of confirmed working systems.

Author: Edwin Groothuis <edwin@FreeBSD.org>
This commit is contained in:
Warner Losh 2010-05-04 06:13:17 +00:00
parent 9182554ae9
commit 752fa69402
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=207607
4 changed files with 700 additions and 606 deletions

View File

@ -1,9 +1,13 @@
# @(#)Makefile 8.1 (Berkeley) 6/6/93
# $FreeBSD$
CFLAGS=-g -Wall
WARNS= 3
PROG= tftp
SRCS= main.c tftp.c tftpsubs.c
SRCS= main.c tftp.c tftp-utils.c tftp-io.c tftp-file.c tftp-transfer.c tftp-options.c
DPADD= ${LIBEDIT} ${LIBTERMCAP}
LDADD= -ledit -ltermcap
CFLAGS+=-I${.CURDIR}/../../libexec/tftpd -I${.CURDIR}/../../usr.bin/tftp
.PATH: ${.CURDIR}/../../libexec/tftpd
.include <bsd.prog.mk>

View File

@ -54,12 +54,14 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/sysctl.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/tftp.h>
#include <ctype.h>
#include <err.h>
@ -72,119 +74,212 @@ __FBSDID("$FreeBSD$");
#include <string.h>
#include <unistd.h>
#include "extern.h"
#include "tftp-utils.h"
#include "tftp-io.h"
#include "tftp-options.h"
#include "tftp.h"
#define MAXLINE 200
#define TIMEOUT 5 /* secs between rexmt's */
struct sockaddr_storage peeraddr;
int f;
int trace;
int verbose;
int connected;
char mode[32];
char line[MAXLINE];
int margc;
#define MAX_MARGV 20
char *margv[MAX_MARGV];
jmp_buf toplevel;
volatile int txrx_error;
static struct sockaddr_storage peeraddr;
static int connected;
static char mode[32];
jmp_buf toplevel;
volatile int txrx_error;
static int peer;
void get(int, char **);
void help(int, char **);
void intr(int);
void modecmd(int, char **);
void put(int, char **);
void quit(int, char **);
void setascii(int, char **);
void setbinary(int, char **);
void setpeer0(char *, const char *);
void setpeer(int, char **);
void setrexmt(int, char **);
void settimeout(int, char **);
void settrace(int, char **);
void setverbose(int, char **);
void status(int, char **);
#define MAX_MARGV 20
static int margc;
static char *margv[MAX_MARGV];
int verbose;
char *port = NULL;
static void get(int, char **);
static void help(int, char **);
static void intr(int);
static void modecmd(int, char **);
static void put(int, char **);
static void quit(int, char **);
static void setascii(int, char **);
static void setbinary(int, char **);
static void setpeer0(char *, const char *);
static void setpeer(int, char **);
static void settimeoutpacket(int, char **);
static void settimeoutnetwork(int, char **);
static void setdebug(int, char **);
static void setverbose(int, char **);
static void showstatus(int, char **);
static void setblocksize(int, char **);
static void setblocksize2(int, char **);
static void setoptions(int, char **);
static void setrollover(int, char **);
static void setpacketdrop(int, char **);
static void command(void) __dead2;
static const char *command_prompt(void);
static void getusage(const char *);
static void makeargv(void);
static void putusage(const char *);
static void urihandling(char *URI);
static void getusage(char *);
static void makeargv(char *line);
static void putusage(char *);
static void settftpmode(const char *);
char *tail(char *);
struct cmd *getcmd(char *);
static char *tail(char *);
static struct cmd *getcmd(char *);
#define HELPINDENT (sizeof("connect"))
struct cmd {
const char *name;
char *help;
void (*handler)(int, char **);
const char *help;
};
char vhelp[] = "toggle verbose mode";
char thelp[] = "toggle packet tracing";
char chelp[] = "connect to remote tftp";
char qhelp[] = "exit tftp";
char hhelp[] = "print help information";
char shelp[] = "send file";
char rhelp[] = "receive file";
char mhelp[] = "set file transfer mode";
char sthelp[] = "show current status";
char xhelp[] = "set per-packet retransmission timeout";
char ihelp[] = "set total retransmission timeout";
char ashelp[] = "set mode to netascii";
char bnhelp[] = "set mode to octet";
static struct cmd cmdtab[] = {
{ "connect", setpeer, "connect to remote tftp" },
{ "mode", modecmd, "set file transfer mode" },
{ "put", put, "send file" },
{ "get", get, "receive file" },
{ "quit", quit, "exit tftp" },
{ "verbose", setverbose, "toggle verbose mode" },
{ "status", showstatus, "show current status" },
{ "binary", setbinary, "set mode to octet" },
{ "ascii", setascii, "set mode to netascii" },
{ "rexmt", settimeoutpacket,
"set per-packet retransmission timeout[-]" },
{ "timeout", settimeoutnetwork,
"set total retransmission timeout" },
{ "trace", setdebug, "enable 'debug packet'[-]" },
{ "debug", setdebug, "enable verbose output" },
{ "blocksize", setblocksize, "set blocksize[*]" },
{ "blocksize2", setblocksize2, "set blocksize as a power of 2[**]" },
{ "rollover", setrollover, "rollover after 64K packets[**]" },
{ "options", setoptions,
"enable or disable RFC2347 style options" },
{ "help", help, "print help information" },
{ "packetdrop", setpacketdrop, "artifical packetloss feature" },
{ "?", help, "print help information" },
{ NULL, NULL, NULL }
};
struct cmd cmdtab[] = {
{ "connect", chelp, setpeer },
{ "mode", mhelp, modecmd },
{ "put", shelp, put },
{ "get", rhelp, get },
{ "quit", qhelp, quit },
{ "verbose", vhelp, setverbose },
{ "trace", thelp, settrace },
{ "status", sthelp, status },
{ "binary", bnhelp, setbinary },
{ "ascii", ashelp, setascii },
{ "rexmt", xhelp, setrexmt },
{ "timeout", ihelp, settimeout },
{ "?", hhelp, help },
{ NULL, NULL, NULL }
static struct modes {
const char *m_name;
const char *m_mode;
} modes[] = {
{ "ascii", "netascii" },
{ "netascii", "netascii" },
{ "binary", "octet" },
{ "image", "octet" },
{ "octet", "octet" },
{ NULL, NULL }
};
int
main(int argc, char *argv[])
{
f = -1;
acting_as_client = 1;
peer = -1;
strcpy(mode, "netascii");
signal(SIGINT, intr);
if (argc > 1) {
if (setjmp(toplevel) != 0)
exit(txrx_error);
if (strncmp(argv[1], "tftp://", 7) == 0) {
urihandling(argv[1]);
exit(txrx_error);
}
setpeer(argc, argv);
}
if (setjmp(toplevel) != 0)
(void)putchar('\n');
init_options();
command();
}
char hostname[MAXHOSTNAMELEN];
/*
* RFC3617 handling of TFTP URIs:
*
* tftpURI = "tftp://" host "/" file [ mode ]
* mode = ";" "mode=" ( "netascii" / "octet" )
* file = *( unreserved / escaped )
* host = <as specified by RFC 2732>
* unreserved = <as specified in RFC 2396>
* escaped = <as specified in RFC 2396>
*
* We are cheating a little bit by allowing any mode as specified in the
* modes table defined earlier on in this file and mapping it on the real
* mode.
*/
static void
urihandling(char *URI)
{
char uri[ARG_MAX];
char *host = NULL;
char *path = NULL;
char *options = NULL;
char *mode = "octet";
char *s;
char line[MAXLINE];
int i;
void
setpeer0(char *host, const char *port)
strncpy(uri, URI, ARG_MAX);
host = uri + 7;
if ((s = strchr(host, '/')) == NULL) {
fprintf(stderr,
"Invalid URI: Couldn't find / after hostname\n");
exit(1);
}
*s = '\0';
path = s + 1;
if ((s = strchr(path, ';')) != NULL) {
*s = '\0';
options = s + 1;
if (strncmp(options, "mode=", 5) == 0) {
mode = options;
mode += 5;
for (i = 0; modes[i].m_name != NULL; i++) {
if (strcmp(modes[i].m_name, mode) == 0)
break;
}
if (modes[i].m_name == NULL) {
fprintf(stderr, "Invalid mode: '%s'\n", mode);
exit(1);
}
settftpmode(modes[i].m_mode);
}
} else {
settftpmode("octet");
}
setpeer0(host, NULL);
sprintf(line, "get %s", path);
makeargv(line);
get(margc, margv);
}
static char hostname[MAXHOSTNAMELEN];
static void
setpeer0(char *host, const char *lport)
{
struct addrinfo hints, *res0, *res;
int error;
struct sockaddr_storage ss;
const char *cause = "unknown";
if (connected) {
close(f);
f = -1;
close(peer);
peer = -1;
}
connected = 0;
@ -193,9 +288,9 @@ setpeer0(char *host, const char *port)
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_CANONNAME;
if (!port)
port = "tftp";
error = getaddrinfo(host, port, &hints, &res0);
if (!lport)
lport = "tftp";
error = getaddrinfo(host, lport, &hints, &res0);
if (error) {
warnx("%s", gai_strerror(error));
return;
@ -204,50 +299,53 @@ setpeer0(char *host, const char *port)
for (res = res0; res; res = res->ai_next) {
if (res->ai_addrlen > sizeof(peeraddr))
continue;
f = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (f < 0) {
peer = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (peer < 0) {
cause = "socket";
continue;
}
memset(&ss, 0, sizeof(ss));
ss.ss_family = res->ai_family;
ss.ss_len = res->ai_addrlen;
if (bind(f, (struct sockaddr *)&ss, ss.ss_len) < 0) {
memset(&peer_sock, 0, sizeof(peer_sock));
peer_sock.ss_family = res->ai_family;
peer_sock.ss_len = res->ai_addrlen;
if (bind(peer, (struct sockaddr *)&peer_sock, peer_sock.ss_len) < 0) {
cause = "bind";
close(f);
f = -1;
close(peer);
peer = -1;
continue;
}
break;
}
if (f < 0)
if (peer < 0)
warn("%s", cause);
else {
/* res->ai_addr <= sizeof(peeraddr) is guaranteed */
memcpy(&peeraddr, res->ai_addr, res->ai_addrlen);
memcpy(&peer_sock, res->ai_addr, res->ai_addrlen);
if (res->ai_canonname) {
(void) strlcpy(hostname, res->ai_canonname,
(void) strncpy(hostname, res->ai_canonname,
sizeof(hostname));
} else
(void) strlcpy(hostname, host, sizeof(hostname));
(void) strncpy(hostname, host, sizeof(hostname));
hostname[sizeof(hostname)-1] = 0;
connected = 1;
}
freeaddrinfo(res0);
}
void
static void
setpeer(int argc, char *argv[])
{
char line[MAXLINE];
if (argc < 2) {
strcpy(line, "Connect ");
printf("(to) ");
fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
makeargv();
makeargv(line);
argc = margc;
argv = margv;
}
@ -255,26 +353,14 @@ setpeer(int argc, char *argv[])
printf("usage: %s [host [port]]\n", argv[0]);
return;
}
if (argc == 3)
if (argc == 3) {
port = argv[2];
setpeer0(argv[1], argv[2]);
else
} else
setpeer0(argv[1], NULL);
}
struct modes {
const char *m_name;
const char *m_mode;
} modes[] = {
{ "ascii", "netascii" },
{ "netascii", "netascii" },
{ "binary", "octet" },
{ "image", "octet" },
{ "octet", "octet" },
/* { "mail", "mail" }, */
{ 0, 0 }
};
void
static void
modecmd(int argc, char *argv[])
{
struct modes *p;
@ -298,7 +384,7 @@ modecmd(int argc, char *argv[])
printf("usage: %s [", argv[0]);
sep = " ";
for (p = modes; p->m_name; p++) {
for (p = modes; p->m_name != NULL; p++) {
printf("%s%s", sep, p->m_name);
if (*sep == ' ')
sep = " | ";
@ -307,14 +393,14 @@ modecmd(int argc, char *argv[])
return;
}
void
static void
setbinary(int argc __unused, char *argv[] __unused)
{
settftpmode("octet");
}
void
static void
setascii(int argc __unused, char *argv[] __unused)
{
@ -324,6 +410,7 @@ setascii(int argc __unused, char *argv[] __unused)
static void
settftpmode(const char *newmode)
{
strcpy(mode, newmode);
if (verbose)
printf("mode set to %s\n", mode);
@ -333,18 +420,20 @@ settftpmode(const char *newmode)
/*
* Send file(s).
*/
void
static void
put(int argc, char *argv[])
{
int fd;
int n;
char *cp, *targ;
int fd;
int n;
char *cp, *targ;
char line[MAXLINE];
struct stat sb;
if (argc < 2) {
strcpy(line, "send ");
printf("(file) ");
fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
makeargv();
makeargv(line);
argc = margc;
argv = margv;
}
@ -381,10 +470,14 @@ put(int argc, char *argv[])
warn("%s", cp);
return;
}
stat(cp, &sb);
asprintf(&options[OPT_TSIZE].o_request, "%ju", sb.st_size);
if (verbose)
printf("putting %s to %s:%s [%s]\n",
cp, hostname, targ, mode);
xmitfile(fd, targ, mode);
cp, hostname, targ, mode);
xmitfile(peer, port, fd, targ, mode);
return;
}
/* this assumes the target is a directory */
@ -398,36 +491,43 @@ put(int argc, char *argv[])
warn("%s", argv[n]);
continue;
}
stat(cp, &sb);
asprintf(&options[OPT_TSIZE].o_request, "%ju", sb.st_size);
if (verbose)
printf("putting %s to %s:%s [%s]\n",
argv[n], hostname, targ, mode);
xmitfile(fd, targ, mode);
argv[n], hostname, targ, mode);
xmitfile(peer, port, fd, targ, mode);
}
}
static void
putusage(const char *s)
putusage(char *s)
{
printf("usage: %s file [[host:]remotename]\n", s);
printf("usage: %s file [remotename]\n", s);
printf(" %s file host:remotename\n", s);
printf(" %s file1 file2 ... fileN [[host:]remote-directory]\n", s);
}
/*
* Receive file(s).
*/
void
static void
get(int argc, char *argv[])
{
int fd;
int n;
char *cp;
char *src;
char line[MAXLINE];
if (argc < 2) {
strcpy(line, "get ");
printf("(files) ");
fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
makeargv();
makeargv(line);
argc = margc;
argv = margv;
}
@ -438,6 +538,8 @@ get(int argc, char *argv[])
if (!connected) {
for (n = 1; n < argc ; n++)
if (rindex(argv[n], ':') == 0) {
printf("No remote host specified and "
"no host given for file '%s'\n", argv[n]);
getusage(argv[0]);
return;
}
@ -468,8 +570,8 @@ get(int argc, char *argv[])
}
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode);
recvfile(fd, src, mode);
hostname, src, cp, mode);
recvfile(peer, port, fd, src, mode);
break;
}
cp = tail(src); /* new .. jdg */
@ -480,30 +582,31 @@ get(int argc, char *argv[])
}
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode);
recvfile(fd, src, mode);
hostname, src, cp, mode);
recvfile(peer, port, fd, src, mode);
}
}
static void
getusage(const char *s)
getusage(char *s)
{
printf("usage: %s [host:]file [localname]\n", s);
printf("usage: %s file [localname]\n", s);
printf(" %s [host:]file [localname]\n", s);
printf(" %s [host1:]file1 [host2:]file2 ... [hostN:]fileN\n", s);
}
int rexmtval = TIMEOUT;
void
setrexmt(int argc, char *argv[])
static void
settimeoutpacket(int argc, char *argv[])
{
int t;
char line[MAXLINE];
if (argc < 2) {
strcpy(line, "Rexmt-timeout ");
strcpy(line, "Packet timeout ");
printf("(value) ");
fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
makeargv();
makeargv(line);
argc = margc;
argv = margv;
}
@ -512,24 +615,25 @@ setrexmt(int argc, char *argv[])
return;
}
t = atoi(argv[1]);
if (t < 0)
if (t < 0) {
printf("%s: bad value\n", argv[1]);
else
rexmtval = t;
return;
}
settimeouts(t, timeoutnetwork, maxtimeouts);
}
int maxtimeout = 5 * TIMEOUT;
void
settimeout(int argc, char *argv[])
static void
settimeoutnetwork(int argc, char *argv[])
{
int t;
char line[MAXLINE];
if (argc < 2) {
strcpy(line, "Maximum-timeout ");
strcpy(line, "Network timeout ");
printf("(value) ");
fgets(&line[strlen(line)], sizeof line - strlen(line), stdin);
makeargv();
makeargv(line);
argc = margc;
argv = margv;
}
@ -538,26 +642,36 @@ settimeout(int argc, char *argv[])
return;
}
t = atoi(argv[1]);
if (t < 0)
if (t < 0) {
printf("%s: bad value\n", argv[1]);
else
maxtimeout = t;
return;
}
settimeouts(timeoutpacket, t, maxtimeouts);
}
void
status(int argc __unused, char *argv[] __unused)
static void
showstatus(int argc __unused, char *argv[] __unused)
{
if (connected)
printf("Connected to %s.\n", hostname);
else
printf("Not connected.\n");
printf("Mode: %s Verbose: %s Tracing: %s\n", mode,
verbose ? "on" : "off", trace ? "on" : "off");
printf("Rexmt-interval: %d seconds, Max-timeout: %d seconds\n",
rexmtval, maxtimeout);
printf("Remote host: %s\n",
connected ? hostname : "none specified yet");
printf("RFC2347 Options support: %s\n",
options_rfc_enabled ? "enabled" : "disabled");
printf("Non-RFC defined options support: %s\n",
options_extra_enabled ? "enabled" : "disabled");
printf("Mode: %s\n", mode);
printf("Verbose: %s\n", verbose ? "on" : "off");
printf("Debug: %s\n", debug_show(debug));
printf("Artificial packetloss: %d in 100 packets\n",
packetdroppercentage);
printf("Segment size: %d bytes\n", segsize);
printf("Network timeout: %d seconds\n", timeoutpacket);
printf("Maximum network timeout: %d seconds\n", timeoutnetwork);
printf("Maximum timeouts: %d \n", maxtimeouts);
}
void
static void
intr(int dummy __unused)
{
@ -566,7 +680,7 @@ intr(int dummy __unused)
longjmp(toplevel, -1);
}
char *
static char *
tail(char *filename)
{
char *s;
@ -583,7 +697,7 @@ tail(char *filename)
}
static const char *
command_prompt(void)
command_prompt()
{
return ("tftp> ");
@ -602,6 +716,7 @@ command(void)
const char *bp;
char *cp;
int len, num, vrbose;
char line[MAXLINE];
vrbose = isatty(0);
if (vrbose) {
@ -623,6 +738,7 @@ command(void)
line[len] = '\0';
history(hist, &he, H_ENTER, bp);
} else {
line[0] = 0;
if (fgets(line, sizeof line , stdin) == 0) {
if (feof(stdin)) {
exit(txrx_error);
@ -635,7 +751,7 @@ command(void)
*cp = '\0';
if (line[0] == 0)
continue;
makeargv();
makeargv(line);
if (margc == 0)
continue;
c = getcmd(margv[0]);
@ -651,7 +767,7 @@ command(void)
}
}
struct cmd *
static struct cmd *
getcmd(char *name)
{
const char *p, *q;
@ -683,15 +799,15 @@ getcmd(char *name)
* Slice a string up into argc/argv.
*/
static void
makeargv(void)
makeargv(char *line)
{
char *cp;
char **argp = margv;
margc = 0;
if ((cp = strchr(line, '\n')))
if ((cp = strchr(line, '\n')) != NULL)
*cp = '\0';
for (cp = line; margc < MAX_MARGV - 1 && *cp;) {
for (cp = line; margc < MAX_MARGV - 1 && *cp != '\0';) {
while (isspace(*cp))
cp++;
if (*cp == '\0')
@ -707,16 +823,17 @@ makeargv(void)
*argp++ = 0;
}
void
static void
quit(int argc __unused, char *argv[] __unused)
{
exit(txrx_error);
}
/*
* Help command.
*/
void
static void
help(int argc, char *argv[])
{
struct cmd *c;
@ -725,6 +842,10 @@ help(int argc, char *argv[])
printf("Commands may be abbreviated. Commands are:\n\n");
for (c = cmdtab; c->name; c++)
printf("%-*s\t%s\n", (int)HELPINDENT, c->name, c->help);
printf("\n[-] : You shouldn't use these ones anymore.\n");
printf("[*] : RFC2834 options support required.\n");
printf("[**] : Non-standard RFC2834 option.\n");
return;
}
while (--argc > 0) {
@ -732,24 +853,211 @@ help(int argc, char *argv[])
arg = *++argv;
c = getcmd(arg);
if (c == (struct cmd *)-1)
printf("?Ambiguous help command %s\n", arg);
printf("?Ambiguous help command: %s\n", arg);
else if (c == (struct cmd *)0)
printf("?Invalid help command %s\n", arg);
printf("?Invalid help command: %s\n", arg);
else
printf("%s\n", c->help);
}
}
void
settrace(int argc __unused, char **argv __unused)
static void
setverbose(int argc __unused, char *argv[] __unused)
{
trace = !trace;
printf("Packet tracing %s.\n", trace ? "on" : "off");
}
void
setverbose(int argc __unused, char **argv __unused)
{
verbose = !verbose;
printf("Verbose mode %s.\n", verbose ? "on" : "off");
}
static void
setoptions(int argc, char *argv[])
{
if (argc == 2) {
if (strcasecmp(argv[1], "enable") == 0 ||
strcasecmp(argv[1], "on") == 0) {
options_extra_enabled = 1;
options_rfc_enabled = 1;
}
if (strcasecmp(argv[1], "disable") == 0 ||
strcasecmp(argv[1], "off") == 0) {
options_extra_enabled = 0;
options_rfc_enabled = 0;
}
if (strcasecmp(argv[1], "extra") == 0)
options_extra_enabled = !options_extra_enabled;
}
printf("Support for RFC2347 style options are now %s.\n",
options_rfc_enabled ? "enabled" : "disabled");
printf("Support for non-RFC defined options are now %s.\n",
options_extra_enabled ? "enabled" : "disabled");
printf("\nThe following options are available:\n"
"\toptions on : enable support for RFC2347 style options\n"
"\toptions off : disable support for RFC2347 style options\n"
"\toptions extra : toggle support for non-RFC defined options\n"
);
}
static void
setrollover(int argc, char *argv[])
{
if (argc == 2) {
if (strcasecmp(argv[1], "never") == 0 ||
strcasecmp(argv[1], "none") == 0) {
free(options[OPT_ROLLOVER].o_request);
options[OPT_ROLLOVER].o_request = NULL;
}
if (strcasecmp(argv[1], "1") == 0) {
free(options[OPT_ROLLOVER].o_request);
options[OPT_ROLLOVER].o_request = strdup("1");
}
if (strcasecmp(argv[1], "0") == 0) {
free(options[OPT_ROLLOVER].o_request);
options[OPT_ROLLOVER].o_request = strdup("0");
}
}
printf("Support for the rollover options is %s.\n",
options[OPT_ROLLOVER].o_request != NULL ? "enabled" : "disabled");
if (options[OPT_ROLLOVER].o_request != NULL)
printf("Block rollover will be to block %s.\n",
options[OPT_ROLLOVER].o_request);
printf("\nThe following rollover options are available:\n"
"\trollover 0 : rollover to block zero (default)\n"
"\trollover 1 : rollover to block one\n"
"\trollover never : do not support the rollover option\n"
"\trollover none : do not support the rollover option\n"
);
}
static void
setdebug(int argc, char *argv[])
{
int i;
if (argc != 1) {
i = 1;
while (i < argc)
debug ^= debug_find(argv[i++]);
}
printf("The following debugging is enabled: %s\n", debug_show(debug));
printf("\nThe following debugs are available:\n");
i = 0;
while (debugs[i].name != NULL) {
printf("\t%s\t%s\n", debugs[i].name, debugs[i].desc);
i++;
}
}
static void
setblocksize(int argc, char *argv[])
{
if (!options_rfc_enabled)
printf("RFC2347 style options are not enabled "
"(but proceding anyway)\n");
if (argc != 1) {
int size = atoi(argv[1]);
size_t max;
char maxbuffer[100];
int *maxdgram;
max = sizeof(maxbuffer);
if (sysctlbyname("net.inet.udp.maxdgram",
maxbuffer, &max, NULL, 0) < 0) {
perror("sysctl: net.inet.udp.maxdgram");
return;
}
maxdgram = (int *)maxbuffer;
if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
printf("Blocksize should be between %d and %d bytes.\n",
BLKSIZE_MIN, BLKSIZE_MAX);
return;
} else if (size > *maxdgram - 4) {
printf("Blocksize can't be bigger than %d bytes due "
"to the net.inet.udp.maxdgram sysctl limitation.\n",
*maxdgram - 4);
asprintf(&options[OPT_BLKSIZE].o_request,
"%d", *maxdgram - 4);
} else {
asprintf(&options[OPT_BLKSIZE].o_request, "%d", size);
}
}
printf("Blocksize is now %s bytes.\n", options[OPT_BLKSIZE].o_request);
}
static void
setblocksize2(int argc, char *argv[])
{
if (!options_rfc_enabled || !options_extra_enabled)
printf(
"RFC2347 style or non-RFC defined options are not enabled "
"(but proceding anyway)\n");
if (argc != 1) {
int size = atoi(argv[1]);
int i;
size_t max;
char maxbuffer[100];
int *maxdgram;
int sizes[] = {
8, 16, 32, 64, 128, 256, 512, 1024,
2048, 4096, 8192, 16384, 32768, 0
};
max = sizeof(maxbuffer);
if (sysctlbyname("net.inet.udp.maxdgram",
maxbuffer, &max, NULL, 0) < 0) {
perror("sysctl: net.inet.udp.maxdgram");
return;
}
maxdgram = (int *)maxbuffer;
for (i = 0; sizes[i] != 0; i++) {
if (sizes[i] == size) break;
}
if (sizes[i] == 0) {
printf("Blocksize2 should be a power of two between "
"8 and 32768.\n");
return;
}
if (size < BLKSIZE_MIN || size > BLKSIZE_MAX) {
printf("Blocksize2 should be between "
"%d and %d bytes.\n", BLKSIZE_MIN, BLKSIZE_MAX);
return;
} else if (size > *maxdgram - 4) {
printf("Blocksize2 can't be bigger than %d bytes due "
"to the net.inet.udp.maxdgram sysctl limitation.\n",
*maxdgram - 4);
for (i = 0; sizes[i+1] != 0; i++) {
if (*maxdgram < sizes[i+1]) break;
}
asprintf(&options[OPT_BLKSIZE2].o_request,
"%d", sizes[i]);
} else {
asprintf(&options[OPT_BLKSIZE2].o_request, "%d", size);
}
}
printf("Blocksize2 is now %s bytes.\n",
options[OPT_BLKSIZE2].o_request);
}
static void
setpacketdrop(int argc, char *argv[])
{
if (argc != 1)
packetdroppercentage = atoi(argv[1]);
printf("Randomly %d in 100 packets will be dropped\n",
packetdroppercentage);
}

View File

@ -37,7 +37,7 @@
.Os
.Sh NAME
.Nm tftp
.Nd "trivial file transfer program"
.Nd trivial file transfer program
.Sh SYNOPSIS
.Nm
.Op Ar host Op Ar port
@ -55,28 +55,26 @@ may be specified on the command line, in which case
uses
.Ar host
as the default host for future transfers (see the
.Ic connect
.Cm connect
command below).
.Sh COMMANDS
Once
.Nm
is running, it issues the prompt
.Dq Li "tftp> "
.Dq Li tftp>
and recognizes the following commands:
.Pp
.Bl -tag -width ".Ic verbose" -compact
.It Ic \&? Ar command-name ...
.Bl -tag -width verbose -compact
.It Cm \&? Ar command-name ...
Print help information.
.Pp
.It Ic ascii
Shorthand for
.Ic mode Cm ascii .
.It Cm ascii
Shorthand for "mode ascii"
.Pp
.It Ic binary
Shorthand for
.Ic mode Cm binary .
.It Cm binary
Shorthand for "mode binary"
.Pp
.It Ic connect Ar host Op Ar port
.It Cm connect Ar host Op Ar port
Set the
.Ar host
(and optionally
@ -88,19 +86,19 @@ protocol, unlike the
.Tn FTP
protocol,
does not maintain connections between transfers; thus, the
.Ic connect
.Cm connect
command does not actually create a connection,
but merely remembers what host is to be used for transfers.
You do not have to use the
.Ic connect
.Cm connect
command; the remote host can be specified as part of the
.Ic get
.Cm get
or
.Ic put
.Cm put
commands.
.Pp
.It Ic get Oo Ar host : Oc Ns Ar file Op Ar localname
.It Ic get Xo
.It Cm get Oo Ar host : Oc Ns Ar file Op Ar localname
.It Cm get Xo
.Oo Ar host1 : Oc Ns Ar file1
.Oo Ar host2 : Oc Ns Ar file2 ...
.Oo Ar hostN : Oc Ns Ar fileN
@ -126,18 +124,18 @@ to disambiguate the
colons used in the IPv6 address from the colon separating the host and
the filename.
.Pp
.It Ic mode Ar transfer-mode
.It Cm mode Ar transfer-mode
Set the mode for transfers;
.Ar transfer-mode
may be one of
.Cm ascii
.Em ascii
or
.Cm binary .
.Em binary .
The default is
.Cm ascii .
.Em ascii .
.Pp
.It Ic put Ar file Op Oo Ar host : Oc Ns Ar remotename
.It Ic put Ar file1 file2 ... fileN Op Oo Ar host : Oc Ns Ar remote-directory
.It Cm put Ar file Op Oo Ar host : Oc Ns Ar remotename
.It Cm put Ar file1 file2 ... fileN Op Oo Ar host : Oc Ns Ar remote-directory
Put a file or set of files to the remote host.
When
.Ar remotename
@ -152,27 +150,27 @@ machine.
To specify an IPv6 numeric address for a
.Ar host ,
see the example under the
.Ic get
.Cm get
command.
.Pp
.It Ic quit
.It Cm quit
Exit
.Nm .
An end of file also exits.
.Pp
.It Ic rexmt Ar retransmission-timeout
.It Cm rexmt Ar retransmission-timeout
Set the per-packet retransmission timeout, in seconds.
.Pp
.It Ic status
.It Cm status
Show current status.
.Pp
.It Ic timeout Ar total-transmission-timeout
.It Cm timeout Ar total-transmission-timeout
Set the total transmission timeout, in seconds.
.Pp
.It Ic trace
.It Cm trace
Toggle packet tracing.
.Pp
.It Ic verbose
.It Cm verbose
Toggle verbose mode.
.El
.Sh HISTORY

View File

@ -45,446 +45,230 @@ __FBSDID("$FreeBSD$");
/*
* TFTP User Program -- Protocol Machines
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <arpa/tftp.h>
#include <err.h>
#include <errno.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include "extern.h"
#include "tftpsubs.h"
extern struct sockaddr_storage peeraddr; /* filled in by main */
extern int f; /* the opened socket */
extern int trace;
extern int verbose;
extern int rexmtval;
extern int maxtimeout;
extern volatile int txrx_error;
#define PKTSIZE SEGSIZE+4
char ackbuf[PKTSIZE];
int timeout;
jmp_buf toplevel;
jmp_buf timeoutbuf;
static void nak(int, const struct sockaddr *);
static int makerequest(int, const char *, struct tftphdr *, const char *);
static void printstats(const char *, unsigned long);
static void startclock(void);
static void stopclock(void);
static void timer(int);
static void tpacket(const char *, struct tftphdr *, int);
static int cmpport(const struct sockaddr *, const struct sockaddr *);
#include "tftp.h"
#include "tftp-file.h"
#include "tftp-utils.h"
#include "tftp-io.h"
#include "tftp-transfer.h"
#include "tftp-options.h"
/*
* Send the requested file.
*/
void
xmitfile(int fd, const char *name, const char *mode)
xmitfile(int peer, char *port, int fd, char *name, char *mode)
{
struct tftphdr *ap; /* data and ack packets */
struct tftphdr *dp;
int n;
volatile unsigned short block;
volatile int size, convert;
volatile unsigned long amount;
struct sockaddr_storage from;
socklen_t fromlen;
FILE *file;
struct sockaddr_storage peer;
struct tftphdr *rp;
int n, i;
uint16_t block;
uint32_t amount;
struct sockaddr_storage serv; /* valid server port number */
char recvbuffer[MAXPKTSIZE];
struct tftp_stats tftp_stats;
stats_init(&tftp_stats);
startclock(); /* start stat's clock */
dp = r_init(); /* reset fillbuf/read-ahead code */
ap = (struct tftphdr *)ackbuf;
file = fdopen(fd, "r");
convert = !strcmp(mode, "netascii");
block = 0;
amount = 0;
memcpy(&peer, &peeraddr, peeraddr.ss_len);
memset(&serv, 0, sizeof(serv));
rp = (struct tftphdr *)recvbuffer;
signal(SIGALRM, timer);
do {
if (block == 0)
size = makerequest(WRQ, name, dp, mode) - 4;
else {
/* size = read(fd, dp->th_data, SEGSIZE); */
size = readit(file, &dp, convert);
if (size < 0) {
nak(errno + 100, (struct sockaddr *)&peer);
break;
}
dp->th_opcode = htons((u_short)DATA);
dp->th_block = htons((u_short)block);
}
timeout = 0;
(void) setjmp(timeoutbuf);
send_data:
if (trace)
tpacket("sent", dp, size + 4);
n = sendto(f, dp, size + 4, 0,
(struct sockaddr *)&peer, peer.ss_len);
if (n != size + 4) {
warn("sendto");
txrx_error = 1;
goto abort;
}
read_ahead(file, convert);
for ( ; ; ) {
alarm(rexmtval);
do {
fromlen = sizeof(from);
n = recvfrom(f, ackbuf, sizeof(ackbuf), 0,
(struct sockaddr *)&from, &fromlen);
} while (n <= 0);
alarm(0);
if (n < 0) {
warn("recvfrom");
txrx_error = 1;
goto abort;
}
if (!serv.ss_family)
serv = from;
else if (!cmpport((struct sockaddr *)&serv,
(struct sockaddr *)&from)) {
warn("server port mismatch");
txrx_error = 1;
goto abort;
}
peer = from;
if (trace)
tpacket("received", ap, n);
/* should verify packet came from server */
ap->th_opcode = ntohs(ap->th_opcode);
ap->th_block = ntohs(ap->th_block);
if (ap->th_opcode == ERROR) {
printf("Error code %d: %s\n", ap->th_code,
ap->th_msg);
txrx_error = 1;
goto abort;
}
if (ap->th_opcode == ACK) {
int j;
if (port == NULL) {
struct servent *se;
se = getservbyname("tftp", "udp");
((struct sockaddr_in *)&peer_sock)->sin_port = se->s_port;
} else
((struct sockaddr_in *)&peer_sock)->sin_port =
htons(atoi(port));
if (ap->th_block == block) {
break;
}
/* On an error, try to synchronize
* both sides.
*/
j = synchnet(f);
if (j && trace) {
printf("discarded %d packets\n",
j);
}
if (ap->th_block == (block-1)) {
goto send_data;
}
}
for (i = 0; i < 12; i++) {
struct sockaddr_storage from;
/* Tell the other side what we want to do */
if (debug&DEBUG_SIMPLE)
printf("Sending %s\n", name);
n = send_wrq(peer, name, mode);
if (n > 0) {
printf("Cannot send WRQ packet\n");
return;
}
if (block > 0)
amount += size;
block++;
} while (size == SEGSIZE || block == 1);
abort:
fclose(file);
stopclock();
/*
* The first packet we receive has the new destination port
* we have to send the next packets to.
*/
n = receive_packet(peer, recvbuffer,
MAXPKTSIZE, &from, timeoutpacket);
/* We got some data! */
if (n >= 0) {
((struct sockaddr_in *)&peer_sock)->sin_port =
((struct sockaddr_in *)&from)->sin_port;
break;
}
/* This should be retried */
if (n == RP_TIMEOUT) {
printf("Try %d, didn't receive answer from remote.\n",
i + 1);
continue;
}
/* Everything else is fatal */
break;
}
if (i == 12) {
printf("Transfer timed out.\n");
return;
}
if (rp->th_opcode == ERROR) {
printf("Got ERROR, aborted\n");
return;
}
/*
* If the first packet is an OACK instead of an ACK packet,
* handle it different.
*/
if (rp->th_opcode == OACK) {
if (!options_rfc_enabled) {
printf("Got OACK while options are not enabled!\n");
send_error(peer, EBADOP);
return;
}
parse_options(peer, rp->th_stuff, n + 2);
}
if (read_init(fd, NULL, mode) < 0) {
warn("read_init()");
return;
}
block = 1;
tftp_send(peer, &block, &tftp_stats);
read_close();
if (amount > 0)
printstats("Sent", amount);
printstats("Sent", verbose, &tftp_stats);
txrx_error = 1;
}
/*
* Receive a file.
*/
void
recvfile(int fd, const char *name, const char *mode)
recvfile(int peer, char *port, int fd, char *name, char *mode)
{
struct tftphdr *ap;
struct tftphdr *dp;
int n;
volatile unsigned short block;
volatile int size, firsttrip;
volatile unsigned long amount;
struct sockaddr_storage from;
socklen_t fromlen;
FILE *file;
volatile int convert; /* true if converting crlf -> lf */
struct sockaddr_storage peer;
struct sockaddr_storage serv; /* valid server port number */
struct tftphdr *rp;
uint16_t block;
char recvbuffer[MAXPKTSIZE];
int n, i;
struct tftp_stats tftp_stats;
startclock();
dp = w_init();
ap = (struct tftphdr *)ackbuf;
file = fdopen(fd, "w");
convert = !strcmp(mode, "netascii");
block = 1;
firsttrip = 1;
amount = 0;
memcpy(&peer, &peeraddr, peeraddr.ss_len);
memset(&serv, 0, sizeof(serv));
stats_init(&tftp_stats);
signal(SIGALRM, timer);
do {
if (firsttrip) {
size = makerequest(RRQ, name, ap, mode);
firsttrip = 0;
} else {
ap->th_opcode = htons((u_short)ACK);
ap->th_block = htons((u_short)(block));
size = 4;
block++;
}
timeout = 0;
(void) setjmp(timeoutbuf);
send_ack:
if (trace)
tpacket("sent", ap, size);
if (sendto(f, ackbuf, size, 0, (struct sockaddr *)&peer,
peer.ss_len) != size) {
alarm(0);
warn("sendto");
txrx_error = 1;
goto abort;
}
write_behind(file, convert);
for ( ; ; ) {
alarm(rexmtval);
do {
fromlen = sizeof(from);
n = recvfrom(f, dp, PKTSIZE, 0,
(struct sockaddr *)&from, &fromlen);
} while (n <= 0);
alarm(0);
if (n < 0) {
warn("recvfrom");
txrx_error = 1;
goto abort;
}
if (!serv.ss_family)
serv = from;
else if (!cmpport((struct sockaddr *)&serv,
(struct sockaddr *)&from)) {
warn("server port mismatch");
txrx_error = 1;
goto abort;
}
peer = from;
if (trace)
tpacket("received", dp, n);
/* should verify client address */
dp->th_opcode = ntohs(dp->th_opcode);
dp->th_block = ntohs(dp->th_block);
if (dp->th_opcode == ERROR) {
printf("Error code %d: %s\n", dp->th_code,
dp->th_msg);
txrx_error = 1;
goto abort;
}
if (dp->th_opcode == DATA) {
int j;
rp = (struct tftphdr *)recvbuffer;
if (dp->th_block == block) {
break; /* have next packet */
}
/* On an error, try to synchronize
* both sides.
*/
j = synchnet(f);
if (j && trace) {
printf("discarded %d packets\n", j);
}
if (dp->th_block == (block-1)) {
goto send_ack; /* resend ack */
}
}
if (port == NULL) {
struct servent *se;
se = getservbyname("tftp", "udp");
((struct sockaddr_in *)&peer_sock)->sin_port = se->s_port;
} else
((struct sockaddr_in *)&peer_sock)->sin_port =
htons(atoi(port));
for (i = 0; i < 12; i++) {
struct sockaddr_storage from;
/* Tell the other side what we want to do */
if (debug&DEBUG_SIMPLE)
printf("Requesting %s\n", name);
n = send_rrq(peer, name, mode);
if (n > 0) {
printf("Cannot send RRQ packet\n");
return;
}
/* size = write(fd, dp->th_data, n - 4); */
size = writeit(file, &dp, n - 4, convert);
if (size < 0) {
nak(errno + 100, (struct sockaddr *)&peer);
/*
* The first packet we receive has the new destination port
* we have to send the next packets to.
*/
n = receive_packet(peer, recvbuffer,
MAXPKTSIZE, &from, timeoutpacket);
/* We got something useful! */
if (n >= 0) {
((struct sockaddr_in *)&peer_sock)->sin_port =
((struct sockaddr_in *)&from)->sin_port;
break;
}
amount += size;
} while (size == SEGSIZE);
abort: /* ok to ack, since user */
ap->th_opcode = htons((u_short)ACK); /* has seen err msg */
ap->th_block = htons((u_short)block);
(void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peer,
peer.ss_len);
write_behind(file, convert); /* flush last buffer */
fclose(file);
stopclock();
if (amount > 0)
printstats("Received", amount);
}
static int
makerequest(int request, const char *name, struct tftphdr *tp, const char *mode)
{
char *cp;
/* We should retry if this happens */
if (n == RP_TIMEOUT) {
printf("Try %d, didn't receive answer from remote.\n",
i + 1);
continue;
}
tp->th_opcode = htons((u_short)request);
cp = tp->th_stuff;
strcpy(cp, name);
cp += strlen(name);
*cp++ = '\0';
strcpy(cp, mode);
cp += strlen(mode);
*cp++ = '\0';
return (cp - (char *)tp);
}
struct errmsg {
int e_code;
const char *e_msg;
} 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" },
{ -1, 0 }
};
/*
* Send a nak packet (error message).
* Error code passed in is one of the
* standard TFTP codes, or a UNIX errno
* offset by 100.
*/
static void
nak(int error, const struct sockaddr *peer)
{
struct errmsg *pe;
struct tftphdr *tp;
int length;
tp = (struct tftphdr *)ackbuf;
tp->th_opcode = htons((u_short)ERROR);
tp->th_code = htons((u_short)error);
for (pe = errmsgs; pe->e_code >= 0; pe++)
if (pe->e_code == error)
break;
if (pe->e_code < 0) {
pe->e_msg = strerror(error - 100);
tp->th_code = EUNDEF;
}
strcpy(tp->th_msg, pe->e_msg);
length = strlen(pe->e_msg) + 4;
if (trace)
tpacket("sent", tp, length);
if (sendto(f, ackbuf, length, 0, peer, peer->sa_len) != length)
warn("nak");
}
static void
tpacket(const char *s, struct tftphdr *tp, int n)
{
static const char *opcodes[] =
{ "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" };
char *cp, *file;
u_short op = ntohs(tp->th_opcode);
if (op < RRQ || op > ERROR)
printf("%s opcode=%x ", s, op);
else
printf("%s %s ", s, opcodes[op]);
switch (op) {
case RRQ:
case WRQ:
n -= 2;
file = cp = tp->th_stuff;
cp = index(cp, '\0');
printf("<file=%s, mode=%s>\n", file, cp + 1);
break;
case DATA:
printf("<block=%d, %d bytes>\n", ntohs(tp->th_block), n - 4);
break;
case ACK:
printf("<block=%d>\n", ntohs(tp->th_block));
break;
case ERROR:
printf("<code=%d, msg=%s>\n", ntohs(tp->th_code), tp->th_msg);
/* Otherwise it is a fatal error */
break;
}
}
struct timeval tstart;
struct timeval tstop;
static void
startclock(void)
{
(void)gettimeofday(&tstart, NULL);
}
static void
stopclock(void)
{
(void)gettimeofday(&tstop, NULL);
}
static void
printstats(const char *direction, unsigned long amount)
{
double delta;
/* compute delta in 1/10's second units */
delta = ((tstop.tv_sec*10.)+(tstop.tv_usec/100000)) -
((tstart.tv_sec*10.)+(tstart.tv_usec/100000));
delta = delta/10.; /* back to seconds */
printf("%s %ld bytes in %.1f seconds", direction, amount, delta);
if (verbose)
printf(" [%.0f bits/sec]", (amount*8.)/delta);
putchar('\n');
}
static void
timer(int sig __unused)
{
timeout += rexmtval;
if (timeout >= maxtimeout) {
printf("Transfer timed out.\n");
longjmp(toplevel, -1);
if (rp->th_opcode == ERROR) {
tftp_log(LOG_ERR, "Error code %d: %s", rp->th_code, rp->th_msg);
return;
}
txrx_error = 1;
longjmp(timeoutbuf, 1);
}
static int
cmpport(const struct sockaddr *sa, const struct sockaddr *sb)
{
char a[NI_MAXSERV], b[NI_MAXSERV];
if (getnameinfo(sa, sa->sa_len, NULL, 0, a, sizeof(a), NI_NUMERICSERV))
return 0;
if (getnameinfo(sb, sb->sa_len, NULL, 0, b, sizeof(b), NI_NUMERICSERV))
return 0;
if (strcmp(a, b) != 0)
return 0;
return 1;
if (write_init(fd, NULL, mode) < 0) {
warn("write_init");
return;
}
stats_init(&tftp_stats);
/*
* If the first packet is an OACK packet instead of an DATA packet,
* handle it different.
*/
if (rp->th_opcode == OACK) {
if (!options_rfc_enabled) {
printf("Got OACK while options are not enabled!\n");
send_error(peer, EBADOP);
return;
}
parse_options(peer, rp->th_stuff, n + 2);
n = send_ack(peer, 0);
if (n > 0) {
printf("Cannot send ACK on OACK.\n");
return;
}
block = 0;
tftp_receive(peer, &block, &tftp_stats, NULL, 0);
} else {
block = 1;
tftp_receive(peer, &block, &tftp_stats, rp, n);
}
write_close();
if (tftp_stats.amount > 0)
printstats("Received", verbose, &tftp_stats);
return;
}