Files
inetutils/src/tftp.c
2025-11-27 00:33:35 +01:00

1395 lines
30 KiB
C

/*
Copyright (C) 1995-2025 Free Software Foundation, Inc.
This file is part of GNU Inetutils.
GNU Inetutils is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or (at
your option) any later version.
GNU Inetutils is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see `http://www.gnu.org/licenses/'. */
/*
* Copyright (c) 1983, 1993
* The Regents of the University of California. 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. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``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 REGENTS OR CONTRIBUTORS 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.
*/
/* Many bug fixes are from Jim Guyton <guyton@rand-unix> */
#include <config.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/time.h>
#include <time.h>
#include <netinet/in.h>
#include <arpa/tftp.h>
#include <arpa/inet.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <netdb.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#if defined HAVE_IDN2_H && defined HAVE_IDN2
# include <idn2.h>
#elif defined HAVE_IDNA_H
# include <idna.h>
#endif
#include <argp.h>
#include <attribute.h>
#include <libinetutils.h>
#include "xalloc.h"
#include "progname.h"
#include "tftpsubs.h"
/* Some systems define PKTSIZE in <arpa/tftp.h>. */
#ifndef PKTSIZE
# define PKTSIZE SEGSIZE+4
#endif
char ackbuf[PKTSIZE];
int timeout;
jmp_buf timeoutbuf;
static void nak (int);
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);
#define TIMEOUT 5 /* secs between rexmt's */
static int rexmtval = TIMEOUT;
static int maxtimeout = 5 * TIMEOUT;
static struct sockaddr_storage peeraddr; /* filled in by main */
static socklen_t peerlen;
static int f = -1; /* the opened socket */
static int port; /* Port number in host byte order of the server. */
static int trace;
static int verbose;
static int connected;
static int fromatty;
char mode[32];
char line[200];
int margc;
#define TFTP_MAX_ARGS 20
char *margv[TFTP_MAX_ARGS];
char *prompt = "tftp";
jmp_buf toplevel;
void intr (int signo);
void get (int, char **);
void help (int, char **);
void modecmd (int, char **);
void put (int, char **);
void quit (int, char **);
void setascii (int, char **);
void setbinary (int, 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 **);
static void command (void);
static void getusage (char *);
static void makeargv (void);
static void putusage (char *);
static void settftpmode (char *);
static in_port_t get_port (struct sockaddr_storage *);
static void set_port (struct sockaddr_storage *, in_port_t);
#define HELPINDENT (sizeof("connect"))
struct cmd
{
char *name;
char *help;
void (*handler) (int, char **);
};
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";
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}
};
struct cmd *getcmd (char *name);
char *tail (char *filename);
const char args_doc[] = "[HOST [PORT]]";
const char doc[] = "Trivial file transfer protocol client";
static struct argp_option argp_options[] = {
{"verbose", 'v', NULL, 0, "verbose output", 1},
{NULL, 0, NULL, 0, NULL, 0}
};
char *hostport_argv[3] = { "connect" };
int hostport_argc = 1;
static in_port_t
get_port (struct sockaddr_storage *ss)
{
switch (ss->ss_family)
{
case AF_INET6:
return ntohs (((struct sockaddr_in6 *) ss)->sin6_port);
break;
case AF_INET:
default:
return ntohs (((struct sockaddr_in *) ss)->sin_port);
break;
}
}
static void
set_port (struct sockaddr_storage *ss, in_port_t port)
{
switch (ss->ss_family)
{
case AF_INET6:
((struct sockaddr_in6 *) ss)->sin6_port = htons (port);
break;
case AF_INET:
default:
((struct sockaddr_in *) ss)->sin_port = htons (port);
break;
}
}
void recvfile (int, char *, char *);
void tftp_sendfile (int, char *, char *);
static error_t
parse_opt (int key, char *arg, struct argp_state *state)
{
switch (key)
{
case 'v': /* Verbose. */
verbose++;
break;
case ARGP_KEY_ARG:
if (state->arg_num >= 2 || hostport_argc >= 3)
/* Too many arguments. */
argp_usage (state);
hostport_argv[hostport_argc++] = arg;
break;
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
static struct argp argp =
{ argp_options, parse_opt, args_doc, doc, NULL, NULL, NULL };
int
main (int argc, char *argv[])
{
struct servent *sp;
set_program_name (argv[0]);
#ifdef HAVE_SETLOCALE
setlocale (LC_ALL, "");
#endif
iu_argp_init ("tftp", default_program_authors);
argp_parse (&argp, argc, argv, 0, NULL, NULL);
/* Initiate a default port. */
sp = getservbyname ("tftp", "udp");
if (sp == 0)
port = 69;
else
port = ntohs (sp->s_port);
fromatty = isatty (STDIN_FILENO);
strcpy (mode, "netascii");
signal (SIGINT, intr);
if (hostport_argc > 1)
{
if (setjmp (toplevel) != 0)
exit (EXIT_SUCCESS);
setpeer (hostport_argc, hostport_argv);
}
if (setjmp (toplevel) != 0)
putchar ('\n');
command ();
}
char *hostname;
#define RESOLVE_OK 0
#define RESOLVE_FAIL -1
/* Resolve NAME. Fill in peeraddr, hostname and set connected on success.
Return value: RESOLVE_OK success
RESOLVE_FAIL error
*/
static int
resolve_name (char *name)
{
int err;
char *rname;
struct sockaddr_storage ss;
struct addrinfo hints, *ai, *aiptr;
#if defined HAVE_IDN || defined HAVE_IDN2
err = idna_to_ascii_lz (name, &rname, 0);
if (err)
{
fprintf (stderr, "tftp: %s: %s\n", name, idna_strerror (err));
return RESOLVE_FAIL;
}
#else /* !HAVE_IDN && !HAVE_IDN2 */
rname = name;
#endif
memset (&hints, 0, sizeof (hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_flags = AI_CANONNAME;
#ifdef AI_IDN
hints.ai_flags |= AI_IDN;
#endif
#ifdef AI_CANONIDN
hints.ai_flags |= AI_CANONIDN;
#endif
err = getaddrinfo (rname, "tftp", &hints, &aiptr);
if (err)
{
fprintf (stderr, "tftp: %s: %s\n", rname, gai_strerror (err));
return RESOLVE_FAIL;
}
#if defined HAVE_IDN || defined HAVE_IDN2
free (rname);
#endif
if (f >= 0)
{
close (f);
f = -1;
}
for (ai = aiptr; ai; ai = ai->ai_next)
{
f = socket (ai->ai_family, ai->ai_socktype, ai->ai_protocol);
if (f < 0)
continue;
memset (&ss, 0, sizeof (ss));
ss.ss_family = ai->ai_family;
#if HAVE_STRUCT_SOCKADDR_STORAGE_SS_LEN
ss.ss_len = ai->ai_addrlen;
#endif
if (bind (f, (struct sockaddr *) &ss, ai->ai_addrlen))
{
close (f);
f = -1;
continue;
}
/* Successfully resolved hostname. */
peerlen = ai->ai_addrlen;
memcpy (&peeraddr, ai->ai_addr, ai->ai_addrlen);
connected = 1;
free (hostname);
if (ai->ai_canonname)
hostname = xstrdup (ai->ai_canonname);
else
hostname = xstrdup ("<dummy>");
break;
}
freeaddrinfo (aiptr);
if (ai == NULL)
return RESOLVE_FAIL;
else
return RESOLVE_OK;
}
/* Prompt for more arguments from the user with PROMPT, putting the results
into ARGC & ARGV, with an initial argument of ARG0. Global variables
LINE, MARGC, and MARGV are changed. */
static void
get_args (char *arg0, char *prompt, int *argc, char ***argv)
{
size_t arg0_len = strlen (arg0);
strcpy (line, arg0);
strcat (line, " ");
printf ("%s", prompt);
if (fgets (line + arg0_len + 1, sizeof line - arg0_len - 1, stdin))
{
makeargv ();
*argc = margc;
*argv = margv;
}
else
{
*argv[0] = arg0;
*argc = 1; /* Will produce a usage printout. */
}
}
void
setpeer (int argc, char *argv[])
{
if (argc < 2)
get_args ("Connect", "(to) ", &argc, &argv);
if (argc < 2 || argc > 3)
{
printf ("usage: %s host-name [port]\n", argv[0]);
return;
}
switch (resolve_name (argv[1]))
{
case RESOLVE_OK:
break;
case RESOLVE_FAIL:
return;
}
if (argc == 3)
{
/* Take a user-specified port number. */
port = atoi (argv[2]);
if (port <= 0)
{
printf ("%s: bad port number\n", argv[2]);
connected = 0;
return;
}
}
else
{
/* Use the standard TFTP port. */
struct servent *sp;
sp = getservbyname ("tftp", "udp");
if (sp == 0)
error (EXIT_FAILURE, 0, "udp/tftp: unknown service\n");
port = ntohs (sp->s_port);
}
connected = 1;
}
struct modes
{
char *m_name;
char *m_mode;
} modes[] = {
{"ascii", "netascii"},
{"netascii", "netascii"},
{"binary", "octet"},
{"image", "octet"},
{"octet", "octet"},
{0, 0}
};
void
modecmd (int argc, char *argv[])
{
struct modes *p;
char *sep;
if (argc < 2)
{
printf ("Using %s mode to transfer files.\n", mode);
return;
}
if (argc == 2)
{
for (p = modes; p->m_name; p++)
if (strcmp (argv[1], p->m_name) == 0)
break;
if (p->m_name)
{
settftpmode (p->m_mode);
return;
}
printf ("%s: unknown mode\n", argv[1]);
/* drop through and print usage message */
}
printf ("usage: %s [", argv[0]);
sep = " ";
for (p = modes; p->m_name; p++)
{
printf ("%s%s", sep, p->m_name);
if (*sep == ' ')
sep = " | ";
}
printf (" ]\n");
return;
}
void
setbinary (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[])
{
settftpmode ("octet");
}
void
setascii (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[])
{
settftpmode ("netascii");
}
static void
settftpmode (char *newmode)
{
strcpy (mode, newmode);
if (verbose)
printf ("mode set to %s\n", mode);
}
/*
* Send file(s).
*/
void
put (int argc, char *argv[])
{
int fd;
int n;
char *cp, *targ;
if (argc < 2)
get_args ("send", "(file) ", &argc, &argv);
if (argc < 2)
{
putusage (argv[0]);
return;
}
targ = argv[argc - 1];
if (strchr (argv[argc - 1], ':'))
{
char *cp;
for (n = 1; n < argc - 1; n++)
if (strchr (argv[n], ':'))
{
putusage (argv[0]);
return;
}
cp = argv[argc - 1];
/* Is host string escaped using square brackets? */
if (cp[0] == '[')
{ /* Locate far end. */
cp = strchr (cp, ']');
if (cp == NULL)
return; /* Unpaired bracket. Fail! */
/* Erase far end and capture valid host string. */
*cp = 0;
targ = cp + 1; /* Position beyond bracket. */
cp = argv[argc - 1] + 1;
/* Verify presence of a colon. Else backup
* in order to accept a strange file name. */
if (targ[0] == ':')
++targ;
else
{
targ = argv[argc - 1];
cp = NULL; /* Invalidate host name string. */
}
}
else
{
targ = strchr (cp, ':');
*targ++ = 0;
/* Test whether host string lacks content.
* Then the host string will be ignored. */
if (strlen (cp) == 0)
cp = NULL; /* Invalidate host name string. */
}
/* If a remote host was stated, resolve it! */
if (cp != NULL && resolve_name (cp) != RESOLVE_OK)
return;
}
if (!connected)
{
printf ("No target machine specified.\n");
return;
}
if (argc < 4)
{
cp = argc == 2 ? tail (targ) : argv[1];
fd = open (cp, O_RDONLY);
if (fd < 0)
{
fprintf (stderr, "tftp: ");
perror (cp);
return;
}
if (verbose)
printf ("putting %s to %s:%s [%s]\n", cp, hostname, targ, mode);
set_port (&peeraddr, port);
tftp_sendfile (fd, targ, mode);
return;
}
/* this assumes the target is a directory */
/* on a remote unix system. hmmmm. */
cp = strchr (targ, '\0');
*cp++ = '/';
for (n = 1; n < argc - 1; n++)
{
strcpy (cp, tail (argv[n]));
fd = open (argv[n], O_RDONLY);
if (fd < 0)
{
fprintf (stderr, "tftp: ");
perror (argv[n]);
continue;
}
if (verbose)
printf ("putting %s to %s:%s [%s]\n", argv[n], hostname, targ, mode);
set_port (&peeraddr, port);
tftp_sendfile (fd, targ, mode);
}
}
static void
putusage (char *s)
{
printf ("usage: %s file ... host:target, or\n", s);
printf (" %s file ... target (when already connected)\n", s);
}
/*
* Receive file(s).
*/
void
get (int argc, char *argv[])
{
int fd;
int n;
char *cp;
char *src;
if (argc < 2)
get_args ("get", "(files) ", &argc, &argv);
if (argc < 2)
{
getusage (argv[0]);
return;
}
if (!connected)
{
for (n = 1; n < argc; n++)
if (strchr (argv[n], ':') == 0)
{
getusage (argv[0]);
return;
}
}
for (n = 1; n < argc; n++)
{
src = strchr (argv[n], ':');
if (src == NULL)
src = argv[n];
else if (src == argv[n])
{
/* Degenerate case: silently drop initial colon.
* No usable host name. */
++src;
}
else
{ /* Parse the host name string; remove square brackets. */
cp = argv[n];
if (cp[0] == '[')
{
cp = strchr (argv[n], ']');
if (cp)
{
/* Calculate host string and source file name. */
src = cp + 1;
*cp = 0;
if (*src == ':')
++src;
cp = argv[n] + 1;
}
}
else
{ /* No escaping; break string at first colon. */
*src++ = 0;
}
if (cp != NULL && resolve_name (cp) != RESOLVE_OK)
continue;
}
if (argc < 4)
{
cp = argc == 3 ? argv[2] : tail (src);
fd = creat (cp, 0644);
if (fd < 0)
{
fprintf (stderr, "tftp: ");
perror (cp);
return;
}
if (verbose)
printf ("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode);
set_port (&peeraddr, port);
recvfile (fd, src, mode);
break;
}
cp = tail (src); /* new .. jdg */
fd = creat (cp, 0644);
if (fd < 0)
{
fprintf (stderr, "tftp: ");
perror (cp);
continue;
}
if (verbose)
printf ("getting from %s:%s to %s [%s]\n", hostname, src, cp, mode);
set_port (&peeraddr, port);
recvfile (fd, src, mode);
}
}
static void
getusage (char *s)
{
printf ("usage: %s host:file host:file ... file, or\n", s);
printf (" %s file file ... file if connected\n", s);
}
void
setrexmt (int argc, char *argv[])
{
int t;
if (argc < 2)
get_args ("Rexmt-timeout", "(value) ", &argc, &argv);
if (argc != 2)
{
printf ("usage: %s value\n", argv[0]);
return;
}
t = atoi (argv[1]);
if (t < 0)
printf ("%s: bad value\n", argv[1]);
else
rexmtval = t;
}
void
settimeout (int argc, char *argv[])
{
int t;
if (argc < 2)
get_args ("Maximum-timeout", "(value) ", &argc, &argv);
if (argc != 2)
{
printf ("usage: %s value\n", argv[0]);
return;
}
t = atoi (argv[1]);
if (t < 0)
printf ("%s: bad value\n", argv[1]);
else
maxtimeout = t;
}
void
status (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[])
{
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);
}
void
intr (int signo MAYBE_UNUSED)
{
signal (SIGALRM, SIG_IGN);
alarm (0);
longjmp (toplevel, -1);
}
char *
tail (char *filename)
{
char *s;
while (*filename)
{
s = strrchr (filename, '/');
if (s == NULL)
break;
if (s[1])
return (s + 1);
*s = '\0';
}
return filename;
}
/*
* Command parser.
*/
static void
command (void)
{
struct cmd *c;
for (;;)
{
if (fromatty)
printf ("%s> ", prompt);
if (fgets (line, sizeof line, stdin) == 0)
{
if (feof (stdin))
exit (EXIT_SUCCESS);
else
continue;
}
if (line[0] == 0)
continue;
makeargv ();
if (margc == 0)
continue;
c = getcmd (margv[0]);
if (c == (struct cmd *) -1)
{
printf ("?Ambiguous command\n");
continue;
}
if (c == 0)
{
printf ("?Invalid command\n");
continue;
}
(*c->handler) (margc, margv);
}
}
struct cmd *
getcmd (char *name)
{
char *p, *q;
struct cmd *c, *found;
int nmatches, longest;
longest = 0;
nmatches = 0;
found = 0;
for (c = cmdtab; (p = c->name) != NULL; c++)
{
for (q = name; *q == *p++; q++)
if (*q == 0) /* exact match? */
return (c);
if (!*q)
{ /* the name was a prefix */
if (q - name > longest)
{
longest = q - name;
nmatches = 1;
found = c;
}
else if (q - name == longest)
nmatches++;
}
}
if (nmatches > 1)
return (struct cmd *) -1;
return found;
}
/*
* Slice a string up into argc/argv.
*/
static void
makeargv (void)
{
char *cp;
char **argp = margv;
margc = 0;
for (cp = line; *cp;)
{
while (isspace (*cp))
cp++;
if (*cp == '\0')
break;
if (margc + 1 >= TFTP_MAX_ARGS)
{
fprintf (stderr, "Ignoring excess arguments.\n");
break;
}
*argp++ = cp;
margc += 1;
while (*cp != '\0' && !isspace (*cp))
cp++;
if (*cp == '\0')
break;
*cp++ = '\0';
}
*argp++ = 0;
}
void
quit (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[])
{
exit (EXIT_SUCCESS);
}
/*
* Help command.
*/
void
help (int argc, char *argv[])
{
struct cmd *c;
if (argc == 1)
{
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);
return;
}
while (--argc > 0)
{
char *arg;
arg = *++argv;
c = getcmd (arg);
if (c == (struct cmd *) -1)
printf ("?Ambiguous help command %s\n", arg);
else if (c == (struct cmd *) 0)
printf ("?Invalid help command %s\n", arg);
else
printf ("%s\n", c->help);
}
}
void
settrace (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[])
{
trace = !trace;
printf ("Packet tracing %s.\n", trace ? "on" : "off");
}
void
setverbose (int argc MAYBE_UNUSED, MAYBE_UNUSED char *argv[])
{
verbose = !verbose;
printf ("Verbose mode %s.\n", verbose ? "on" : "off");
}
/*
* Send the requested file.
*/
void
tftp_sendfile (int fd, char *name, char *mode)
{
struct tftphdr *ap; /* data and ack packets */
struct tftphdr *r_init (void), *dp;
int n;
volatile int block, size, convert;
volatile unsigned long amount;
struct sockaddr_storage from;
socklen_t fromlen;
FILE *file;
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;
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);
break;
}
dp->th_opcode = htons ((unsigned short) DATA);
dp->th_block = htons ((unsigned short) block);
}
timeout = 0;
setjmp (timeoutbuf);
send_data:
if (trace)
tpacket ("sent", dp, size + 4);
n = sendto (f, (const char *) dp, size + 4, 0,
(struct sockaddr *) &peeraddr, peerlen);
if (n != size + 4)
{
perror ("tftp: sendto");
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)
{
perror ("tftp: recvfrom");
goto abort;
}
set_port (&peeraddr, get_port (&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);
goto abort;
}
if (ap->th_opcode == ACK)
{
int j;
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;
}
}
if (block > 0)
amount += size;
block++;
}
while (size == SEGSIZE || block == 1);
abort:
fclose (file);
stopclock ();
if (amount > 0)
printstats ("Sent", amount);
}
/*
* Receive a file.
*/
void
recvfile (int fd, char *name, char *mode)
{
struct tftphdr *ap;
struct tftphdr *dp, *w_init (void);
int n;
volatile int block, size, firsttrip;
volatile unsigned long amount;
struct sockaddr_storage from;
socklen_t fromlen;
FILE *file;
volatile int convert; /* true if converting crlf -> lf */
startclock ();
dp = w_init ();
ap = (struct tftphdr *) ackbuf;
file = fdopen (fd, "w");
convert = !strcmp (mode, "netascii");
block = 1;
firsttrip = 1;
amount = 0;
signal (SIGALRM, timer);
do
{
if (firsttrip)
{
size = makerequest (RRQ, name, ap, mode);
firsttrip = 0;
}
else
{
ap->th_opcode = htons ((unsigned short) ACK);
ap->th_block = htons ((unsigned short) (block));
size = 4;
block++;
}
timeout = 0;
setjmp (timeoutbuf);
send_ack:
if (trace)
tpacket ("sent", ap, size);
if (sendto (f, ackbuf, size, 0, (struct sockaddr *) &peeraddr,
peerlen) != size)
{
alarm (0);
perror ("tftp: sendto");
goto abort;
}
write_behind (file, convert);
for (;;)
{
alarm (rexmtval);
do
{
fromlen = sizeof (from);
n = recvfrom (f, (char *) dp, PKTSIZE, 0,
(struct sockaddr *) &from, &fromlen);
}
while (n <= 0);
alarm (0);
if (n < 0)
{
perror ("tftp: recvfrom");
goto abort;
}
set_port (&peeraddr, get_port (&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);
goto abort;
}
if (dp->th_opcode == DATA)
{
int j;
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 */
}
}
/* size = write(fd, dp->th_data, n - 4); */
size = writeit (file, &dp, n - 4, convert);
if (size < 0)
{
nak (errno + 100);
break;
}
amount += size;
}
while (size == SEGSIZE);
abort: /* ok to ack, since user */
ap->th_opcode = htons ((unsigned short) ACK); /* has seen err msg */
ap->th_block = htons ((unsigned short) block);
sendto (f, ackbuf, 4, 0, (struct sockaddr *) &peeraddr, peerlen);
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;
size_t arglen, len;
tp->th_opcode = htons ((unsigned short) request);
#if HAVE_STRUCT_TFTPHDR_TH_U
/*
* GNU and BSD essentially, i.e. modulo a macro, define
* 'tftphdr.th_stuff' as a character array of length
* naught with GNU, and one with BSD!
*
* When compiling with stack protectors, like during
* hardened builds in Debian, every useful file name
* will overflow that limit. However, our code ensures
* '*tp' to be of length PKTSIZE. Assigning CP via an
* offset calculation avoids this issue.
*/
cp = (char *) tp + (tp->th_stuff - (char *) tp);
#else
cp = (char *) &(tp->th_stuff);
#endif
/* Available space for naming the target file. */
len = PKTSIZE - sizeof (struct tftphdr) - sizeof ("netascii");
arglen = strlen (name);
strncpy (cp, name, len);
cp += (arglen < len) ? arglen : len;
*cp++ = '\0';
/* Mode is either "netascii" or "octet", so is always fits
* based on our choice of LEN. */
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)
{
struct errmsg *pe;
struct tftphdr *tp;
int length;
tp = (struct tftphdr *) ackbuf;
tp->th_opcode = htons ((unsigned short) ERROR);
tp->th_code = htons ((unsigned 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;
}
length = strlen (pe->e_msg) + 4;
memcpy (tp->th_msg, pe->e_msg, length - 3);
if (trace)
tpacket ("sent", tp, length);
if (sendto (f, ackbuf, length, 0, (struct sockaddr *) &peeraddr,
peerlen) != length)
perror ("nak");
}
static void
tpacket (const char *s, struct tftphdr *tp, int n)
{
static char *opcodes[] = { "#0", "RRQ", "WRQ", "DATA", "ACK", "ERROR" };
char *cp, *file;
unsigned 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;
#if HAVE_STRUCT_TFTPHDR_TH_U
file = cp = tp->th_stuff;
#else
file = cp = (char *) &(tp->th_stuff);
#endif
cp = strchr (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);
break;
}
}
struct timeval tstart;
struct timeval tstop;
static void
startclock (void)
{
gettimeofday (&tstart, NULL);
}
static void
stopclock (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 %d bytes in %.1f seconds", direction, (int) amount, delta);
if (verbose)
printf (" [%.0f bits/sec]", (amount * 8.) / delta);
putchar ('\n');
}
static void
timer (int sig MAYBE_UNUSED)
{
timeout += rexmtval;
if (timeout >= maxtimeout)
{
printf ("Transfer timed out.\n");
longjmp (toplevel, -1);
}
longjmp (timeoutbuf, 1);
}