mirror of
https://git.savannah.gnu.org/git/inetutils.git
synced 2026-01-12 00:19:39 +08:00
1395 lines
30 KiB
C
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);
|
|
}
|