Add support for IPv6 in the server and client.

Add support for IPv6 in the server and client.
You can force the use of IPv4 or IPv6 only with new
-4 and -6 commandline options, if IPv6 support was compiled in.

Signed-off-by: Karsten Keil <kkeil@suse.de>
Signed-off-by: H. Peter Anvin <hpa@zytor.com>
This commit is contained in:
Karsten Keil 2008-07-23 18:32:21 +02:00 committed by H. Peter Anvin
parent 7fe0fb941c
commit 28f22b6591
9 changed files with 574 additions and 185 deletions

View file

@ -237,7 +237,7 @@ int synchnet(int f)
{ /* socket to flush */
int pktcount = 0;
char rbuf[PKTSIZE];
struct sockaddr_in from;
union sock_addr from;
socklen_t fromlen;
fd_set socketset;
struct timeval notime;
@ -253,15 +253,15 @@ int synchnet(int f)
/* Otherwise drain the packet */
pktcount++;
fromlen = sizeof from;
fromlen = sizeof(from);
(void)recvfrom(f, rbuf, sizeof(rbuf), 0,
(struct sockaddr *)&from, &fromlen);
&from.sa, &fromlen);
}
return pktcount; /* Return packets drained */
}
int pick_port_bind(int sockfd, struct sockaddr_in *myaddr,
int pick_port_bind(int sockfd, union sock_addr *myaddr,
unsigned int port_range_from,
unsigned int port_range_to)
{
@ -279,9 +279,8 @@ int pick_port_bind(int sockfd, struct sockaddr_in *myaddr,
port = firstport;
do {
myaddr->sin_port = htons(port);
if (bind(sockfd, (struct sockaddr *)myaddr, sizeof *myaddr) < 0) {
sa_set_port(myaddr, htons(port));
if (bind(sockfd, &myaddr->sa, SOCKLEN(myaddr)) < 0) {
/* Some versions of Linux return EINVAL instead of EADDRINUSE */
if (!(port_range && (errno == EINVAL || errno == EADDRINUSE)))
return -1;
@ -299,3 +298,34 @@ int pick_port_bind(int sockfd, struct sockaddr_in *myaddr,
return -1;
}
int
set_sock_addr(char *host,union sock_addr *s, char **name)
{
struct addrinfo *addrResult;
struct addrinfo hints;
int err;
memset(&hints, 0, sizeof(hints));
hints.ai_family = s->sa.sa_family;
hints.ai_flags = AI_CANONNAME;
err = getaddrinfo(host, NULL, &hints, &addrResult);
if (err) {
printf("Error : %s\n", gai_strerror(err));
printf("%s: unknown host\n", host);
return err;
}
if (addrResult == NULL) {
printf("%s: unknown host\n", host);
return EAI_NONAME;
}
memcpy(s, addrResult->ai_addr, addrResult->ai_addrlen);
if (name) {
if (addrResult->ai_canonname)
*name = xstrdup(addrResult->ai_canonname);
else
*name = xstrdup(host);
}
freeaddrinfo(addrResult);
return 0;
}

View file

@ -40,6 +40,58 @@
#include "config.h"
union sock_addr {
struct sockaddr sa;
struct sockaddr_in si;
#ifdef HAVE_IPV6
struct sockaddr_in6 s6;
#endif
};
#define SOCKLEN(sock) \
(((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
(sizeof(struct sockaddr_in)) : \
(sizeof(union sock_addr)))
#ifdef HAVE_IPV6
#define SOCKPORT(sock) \
(((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
((union sock_addr*)sock)->si.sin_port : \
((union sock_addr*)sock)->s6.sin6_port)
#else
#define SOCKPORT(sock) \
(((union sock_addr*)sock)->si.sin_port)
#endif
#ifdef HAVE_IPV6
#define SOCKADDR_P(sock) \
(((union sock_addr*)sock)->sa.sa_family == AF_INET ? \
(void *)&((union sock_addr*)sock)->si.sin_addr : \
(void *)&((union sock_addr*)sock)->s6.sin6_addr)
#else
#define SOCKADDR_P(sock) \
(void *)&((union sock_addr*)sock)->si.sin_addr
#endif
static inline int sa_set_port(union sock_addr *s, u_short port)
{
switch (s->sa.sa_family) {
case AF_INET:
s->si.sin_port = port;
break;
#ifdef HAVE_IPV6
case AF_INET6:
s->s6.sin6_port = port;
break;
#endif
default:
return -1;
}
return 0;
}
int set_sock_addr(char *, union sock_addr *, char **);
struct tftphdr;
struct tftphdr *r_init(void);
@ -55,7 +107,7 @@ int writeit(FILE *, struct tftphdr **, int, int);
extern int segsize;
#define MAX_SEGSIZE 65464
int pick_port_bind(int sockfd, struct sockaddr_in *myaddr,
int pick_port_bind(int sockfd, union sock_addr *myaddr,
unsigned int from, unsigned int to);
#endif

View file

@ -40,7 +40,6 @@
*/
#include <sys/file.h>
#include <ctype.h>
#include <netdb.h>
#ifdef WITH_READLINE
#include <readline/readline.h>
#ifdef HAVE_READLINE_HISTORY_H
@ -72,8 +71,16 @@ static const struct modes modes[] = {
#define MODE_NETASCII (&modes[0])
#define MODE_DEFAULT MODE_NETASCII
struct sockaddr_in peeraddr;
int f;
#ifdef HAVE_IPV6
int ai_fam = AF_UNSPEC;
int ai_fam_sock = AF_UNSPEC;
#else
int ai_fam = AF_INET;
int ai_fam_sock = AF_INET;
#endif
union sock_addr peeraddr;
int f = -1;
u_short port;
int trace;
int verbose;
@ -184,14 +191,18 @@ const char *program;
static inline void usage(int errcode)
{
fprintf(stderr,
#ifdef HAVE_IPV6
"Usage: %s [-4][-6][-v][-l][-m mode] [host [port]] [-c command]\n",
#else
"Usage: %s [-v][-l][-m mode] [host [port]] [-c command]\n",
#endif
program);
exit(errcode);
}
int main(int argc, char *argv[])
{
struct sockaddr_in s_in;
union sock_addr sa;
int arg;
static int pargc, peerargc;
static int iscmd = 0;
@ -210,6 +221,14 @@ int main(int argc, char *argv[])
if (argv[arg][0] == '-') {
for (optx = &argv[arg][1]; *optx; optx++) {
switch (*optx) {
#ifdef HAVE_IPV6
case '4':
ai_fam = AF_INET;
break;
case '6':
ai_fam = AF_INET6;
break;
#endif
case 'v':
verbose = 1;
break;
@ -268,6 +287,8 @@ int main(int argc, char *argv[])
}
}
ai_fam_sock = ai_fam;
pargv = argv + arg;
pargc = argc - arg;
@ -283,18 +304,7 @@ int main(int argc, char *argv[])
sp->s_port = htons(IPPORT_TFTP);
sp->s_proto = (char *)"udp";
}
port = sp->s_port; /* Default port */
f = socket(AF_INET, SOCK_DGRAM, 0);
if (f < 0) {
perror("tftp: socket");
exit(EX_OSERR);
}
bzero((char *)&s_in, sizeof(s_in));
s_in.sin_family = AF_INET;
if (pick_port_bind(f, &s_in, portrange_from, portrange_to)) {
perror("tftp: bind");
exit(EX_OSERR);
}
bsd_signal(SIGINT, intr);
if (peerargc) {
@ -304,6 +314,21 @@ int main(int argc, char *argv[])
setpeer(peerargc, peerargv);
}
if (ai_fam_sock == AF_UNSPEC)
ai_fam_sock = AF_INET;
f = socket(ai_fam_sock, SOCK_DGRAM, 0);
if (f < 0) {
perror("tftp: socket");
exit(EX_OSERR);
}
bzero(&sa, sizeof(sa));
sa.sa.sa_family = ai_fam_sock;
if (pick_port_bind(f, &sa, portrange_from, portrange_to)) {
perror("tftp: bind");
exit(EX_OSERR);
}
if (iscmd && pargc) {
/* -c specified; execute command and exit */
struct cmd *c;
@ -375,7 +400,7 @@ static void getmoreargs(const char *partial, const char *mprompt)
void setpeer(int argc, char *argv[])
{
struct hostent *host;
int err;
if (argc < 2) {
getmoreargs("connect ", "(to) ");
@ -388,16 +413,34 @@ void setpeer(int argc, char *argv[])
return;
}
host = gethostbyname(argv[1]);
if (host == 0) {
peeraddr.sa.sa_family = ai_fam;
err = set_sock_addr(argv[1], &peeraddr, &hostname);
if (err) {
connected = 0;
printf("%s: unknown host\n", argv[1]);
return;
}
peeraddr.sin_family = host->h_addrtype;
bcopy(host->h_addr, &peeraddr.sin_addr, host->h_length);
hostname = xstrdup(host->h_name);
ai_fam = peeraddr.sa.sa_family;
if (f == -1) { /* socket not open */
ai_fam_sock = ai_fam;
} else { /* socket was already open */
if (ai_fam_sock != ai_fam) { /* need reopen socken for new family */
union sock_addr sa;
close(f);
ai_fam_sock = ai_fam;
f = socket(ai_fam_sock, SOCK_DGRAM, 0);
if (f < 0) {
perror("tftp: socket");
exit(EX_OSERR);
}
bzero((char *)&sa, sizeof (sa));
sa.sa.sa_family = ai_fam_sock;
if (pick_port_bind(f, &sa, portrange_from, portrange_to)) {
perror("tftp: bind");
exit(EX_OSERR);
}
}
}
port = sp->s_port;
if (argc == 3) {
struct servent *usp;
@ -418,9 +461,13 @@ void setpeer(int argc, char *argv[])
}
if (verbose) {
char tmp[INET6_ADDRSTRLEN], *tp;
tp = (char *)inet_ntop(peeraddr.sa.sa_family, SOCKADDR_P(&peeraddr),
tmp, INET6_ADDRSTRLEN);
if (!tp)
tp = (char *)"???";
printf("Connected to %s (%s), port %u\n",
hostname, inet_ntoa(peeraddr.sin_addr),
(unsigned int)ntohs(port));
hostname, tp, (unsigned int)ntohs(port));
}
connected = 1;
}
@ -484,7 +531,7 @@ static void settftpmode(const struct modes *newmode)
void put(int argc, char *argv[])
{
int fd;
int n;
int n, err;
char *cp, *targ;
if (argc < 2) {
@ -499,8 +546,6 @@ void put(int argc, char *argv[])
}
targ = argv[argc - 1];
if (!literal && strchr(argv[argc - 1], ':')) {
struct hostent *hp;
for (n = 1; n < argc - 1; n++)
if (strchr(argv[n], ':')) {
putusage(argv[0]);
@ -509,16 +554,14 @@ void put(int argc, char *argv[])
cp = argv[argc - 1];
targ = strchr(cp, ':');
*targ++ = 0;
hp = gethostbyname(cp);
if (hp == NULL) {
fprintf(stderr, "tftp: %s: ", cp);
herror((char *)NULL);
peeraddr.sa.sa_family = ai_fam;
err = set_sock_addr(cp, &peeraddr,&hostname);
if (err) {
connected = 0;
return;
}
bcopy(hp->h_addr, &peeraddr.sin_addr, hp->h_length);
peeraddr.sin_family = hp->h_addrtype;
ai_fam = peeraddr.sa.sa_family;
connected = 1;
hostname = xstrdup(hp->h_name);
}
if (!connected) {
printf("No target machine specified.\n");
@ -535,7 +578,7 @@ void put(int argc, char *argv[])
if (verbose)
printf("putting %s to %s:%s [%s]\n",
cp, hostname, targ, mode->m_mode);
peeraddr.sin_port = port;
sa_set_port(&peeraddr, port);
tftp_sendfile(fd, targ, mode->m_mode);
return;
}
@ -554,7 +597,7 @@ void put(int argc, char *argv[])
if (verbose)
printf("putting %s to %s:%s [%s]\n",
argv[n], hostname, targ, mode->m_mode);
peeraddr.sin_port = port;
sa_set_port(&peeraddr, port);
tftp_sendfile(fd, targ, mode->m_mode);
}
}
@ -597,19 +640,15 @@ void get(int argc, char *argv[])
if (literal || src == NULL)
src = argv[n];
else {
struct hostent *hp;
int err;
*src++ = 0;
hp = gethostbyname(argv[n]);
if (hp == NULL) {
fprintf(stderr, "tftp: %s: ", argv[n]);
herror((char *)NULL);
peeraddr.sa.sa_family = ai_fam;
err = set_sock_addr(argv[n], &peeraddr, &hostname);
if (err)
continue;
}
bcopy(hp->h_addr, (caddr_t) & peeraddr.sin_addr, hp->h_length);
peeraddr.sin_family = hp->h_addrtype;
ai_fam = peeraddr.sa.sa_family;
connected = 1;
hostname = xstrdup(hp->h_name);
}
if (argc < 4) {
cp = argc == 3 ? argv[2] : tail(src);
@ -623,7 +662,7 @@ void get(int argc, char *argv[])
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode->m_mode);
peeraddr.sin_port = port;
sa_set_port(&peeraddr, port);
tftp_recvfile(fd, src, mode->m_mode);
break;
}
@ -638,7 +677,7 @@ void get(int argc, char *argv[])
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode->m_mode);
peeraddr.sin_port = port;
sa_set_port(&peeraddr, port);
tftp_recvfile(fd, src, mode->m_mode);
}
}

View file

@ -42,7 +42,7 @@
.br
.SH DESCRIPTION
.B tftp
is a client for the IPv4 Trivial file Transfer Protocol, which can be
is a client for the Trivial file Transfer Protocol, which can be
used to transfer files to and from remote machines, including some
very minimalistic, usually embedded, systems. The remote
.I host
@ -55,6 +55,11 @@ as the default host for future transfers (see the
command below.)
.SH OPTIONS
.TP
.B \-4
Connect with IPv4 only, if IPv6 support was compiled in.
.TP
.B \-6
.TP
\fB\-c\fP \fIcommand\fP
Execute \fIcommand\fP as if it had been entered on the tftp prompt.
Must be specified last on the command line.

View file

@ -38,8 +38,8 @@
*/
#include "extern.h"
extern struct sockaddr_in peeraddr; /* filled in by main */
extern int f; /* the opened socket */
extern union sock_addr peeraddr; /* filled in by main */
extern int f; /* the opened socket */
extern int trace;
extern int verbose;
extern int rexmtval;
@ -71,7 +71,7 @@ void tftp_sendfile(int fd, const char *name, const char *mode)
volatile u_short block;
volatile int size, convert;
volatile off_t amount;
struct sockaddr_in from;
union sock_addr from;
socklen_t fromlen;
FILE *file;
u_short ap_opcode, ap_block;
@ -105,7 +105,7 @@ void tftp_sendfile(int fd, const char *name, const char *mode)
if (trace)
tpacket("sent", dp, size + 4);
n = sendto(f, dp, size + 4, 0,
(struct sockaddr *)&peeraddr, sizeof(peeraddr));
&peeraddr.sa, SOCKLEN(&peeraddr));
if (n != size + 4) {
perror("tftp: sendto");
goto abort;
@ -116,14 +116,14 @@ void tftp_sendfile(int fd, const char *name, const char *mode)
do {
fromlen = sizeof(from);
n = recvfrom(f, ackbuf, sizeof(ackbuf), 0,
(struct sockaddr *)&from, &fromlen);
&from.sa, &fromlen);
} while (n <= 0);
alarm(0);
if (n < 0) {
perror("tftp: recvfrom");
goto abort;
}
peeraddr.sin_port = from.sin_port; /* added */
sa_set_port(&peeraddr, SOCKPORT(&from)); /* added */
if (trace)
tpacket("received", ap, n);
/* should verify packet came from server */
@ -176,7 +176,7 @@ void tftp_recvfile(int fd, const char *name, const char *mode)
volatile u_short block;
volatile int size, firsttrip;
volatile unsigned long amount;
struct sockaddr_in from;
union sock_addr from;
socklen_t fromlen;
FILE *file;
volatile int convert; /* true if converting crlf -> lf */
@ -207,8 +207,8 @@ void tftp_recvfile(int fd, const char *name, const char *mode)
send_ack:
if (trace)
tpacket("sent", ap, size);
if (sendto(f, ackbuf, size, 0, (struct sockaddr *)&peeraddr,
sizeof(peeraddr)) != size) {
if (sendto(f, ackbuf, size, 0, &peeraddr.sa,
SOCKLEN(&peeraddr)) != size) {
alarm(0);
perror("tftp: sendto");
goto abort;
@ -219,14 +219,14 @@ void tftp_recvfile(int fd, const char *name, const char *mode)
do {
fromlen = sizeof(from);
n = recvfrom(f, dp, PKTSIZE, 0,
(struct sockaddr *)&from, &fromlen);
&from.sa, &fromlen);
} while (n <= 0);
alarm(0);
if (n < 0) {
perror("tftp: recvfrom");
goto abort;
}
peeraddr.sin_port = from.sin_port; /* added */
sa_set_port(&peeraddr, SOCKPORT(&from)); /* added */
if (trace)
tpacket("received", dp, n);
/* should verify client address */
@ -266,7 +266,7 @@ void tftp_recvfile(int fd, const char *name, const char *mode)
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 *)&peeraddr,
sizeof(peeraddr));
SOCKLEN(&peeraddr));
write_behind(file, convert); /* flush last buffer */
fclose(file);
stopclock();
@ -341,8 +341,8 @@ static void nak(int error, const char *msg)
if (trace)
tpacket("sent", tp, length);
if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr,
sizeof(peeraddr)) != length)
if (sendto(f, ackbuf, length, 0, &peeraddr.sa,
SOCKLEN(&peeraddr)) != length)
perror("nak");
}

View file

@ -18,8 +18,8 @@
*/
#include "config.h" /* Must be included first! */
#include "recvfrom.h"
#include "common/tftpsubs.h"
#include "recvfrom.h"
#ifdef HAVE_MACHINE_PARAM_H
#include <machine/param.h> /* Needed on some versions of FreeBSD */
#endif
@ -55,31 +55,47 @@ struct in_pktinfo {
* end up having the same local and remote address when trying to
* bind to it.
*/
static int address_is_local(const struct sockaddr_in *addr)
static int address_is_local(const union sock_addr *addr)
{
struct sockaddr_in sin;
union sock_addr sa;
int sockfd = -1;
int e;
int rv = 0;
socklen_t addrlen;
/* Multicast or universal broadcast address? */
if (ntohl(addr->sin_addr.s_addr) >= (224UL << 24))
if (addr->sa.sa_family == AF_INET) {
if (ntohl(addr->si.sin_addr.s_addr) >= (224UL << 24))
return 0;
}
#ifdef HAVE_IPV6
else if (addr->sa.sa_family == AF_INET6) {
if (IN6_IS_ADDR_MULTICAST(&addr->s6.sin6_addr))
return 0;
}
#endif
else
return 0;
sockfd = socket(PF_INET, SOCK_DGRAM, 0);
sockfd = socket(addr->sa.sa_family, SOCK_DGRAM, 0);
if (sockfd < 0)
goto err;
if (connect(sockfd, (const struct sockaddr *)addr, sizeof *addr))
if (connect(sockfd, &addr->sa, SOCKLEN(addr)))
goto err;
addrlen = sizeof sin;
if (getsockname(sockfd, (struct sockaddr *)&sin, &addrlen))
addrlen = SOCKLEN(addr);
if (getsockname(sockfd, (struct sockaddr *)&sa, &addrlen))
goto err;
rv = sin.sin_addr.s_addr == addr->sin_addr.s_addr;
if (addr->sa.sa_family == AF_INET)
rv = sa.si.sin_addr.s_addr == addr->si.sin_addr.s_addr;
#ifdef HAVE_IPV6
else if (addr->sa.sa_family == AF_INET6)
rv = IN6_ARE_ADDR_EQUAL(&sa.s6.sin6_addr, &addr->s6.sin6_addr);
#endif
else
rv = 0;
err:
e = errno;
@ -93,7 +109,7 @@ static int address_is_local(const struct sockaddr_in *addr)
int
myrecvfrom(int s, void *buf, int len, unsigned int flags,
struct sockaddr *from, socklen_t * fromlen,
struct sockaddr_in *myaddr)
union sock_addr *myaddr)
{
struct msghdr msg;
struct iovec iov;
@ -106,24 +122,42 @@ myrecvfrom(int s, void *buf, int len, unsigned int flags,
CMSG_SPACE(sizeof(struct in_pktinfo))];
#else
char control[CMSG_SPACE(sizeof(struct in_addr))];
#endif
#ifdef HAVE_IPV6
#ifdef HAVE_STRUCT_IN6_PKTINFO
char control6[CMSG_SPACE(sizeof(struct in6_addr)) +
CMSG_SPACE(sizeof(struct in6_pktinfo))];
#else
char control6[CMSG_SPACE(sizeof(struct in6_addr))];
#endif
#endif
} control_un;
int on = 1;
#ifdef IP_PKTINFO
struct in_pktinfo pktinfo;
#endif
#ifdef HAVE_STRUCT_IN6_PKTINFO
struct in6_pktinfo pktinfo6;
#endif
/* Try to enable getting the return address */
#ifdef IP_RECVDSTADDR
setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on));
if (from->sa_family == AF_INET)
setsockopt(s, IPPROTO_IP, IP_RECVDSTADDR, &on, sizeof(on));
#endif
#ifdef IP_PKTINFO
setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
if (from->sa_family == AF_INET)
setsockopt(s, IPPROTO_IP, IP_PKTINFO, &on, sizeof(on));
#endif
#ifdef HAVE_IPV6
#ifdef IPV6_RECVPKTINFO
if (from->sa_family == AF_INET6)
setsockopt(s, IPPROTO_IPV6, IPV6_RECVPKTINFO, &on, sizeof(on));
#endif
#endif
bzero(&msg, sizeof msg); /* Clear possible system-dependent fields */
msg.msg_control = control_un.control;
msg.msg_controllen = sizeof(control_un.control);
msg.msg_controllen = sizeof(control_un);
msg.msg_flags = 0;
msg.msg_name = from;
@ -139,8 +173,8 @@ myrecvfrom(int s, void *buf, int len, unsigned int flags,
*fromlen = msg.msg_namelen;
if (myaddr) {
bzero(myaddr, sizeof(struct sockaddr_in));
myaddr->sin_family = AF_INET;
bzero(myaddr, sizeof(*myaddr));
myaddr->sa.sa_family = from->sa_family;
if (msg.msg_controllen < sizeof(struct cmsghdr) ||
(msg.msg_flags & MSG_CTRUNC))
@ -149,31 +183,61 @@ myrecvfrom(int s, void *buf, int len, unsigned int flags,
for (cmptr = CMSG_FIRSTHDR(&msg); cmptr != NULL;
cmptr = CMSG_NXTHDR(&msg, cmptr)) {
if (from->sa_family == AF_INET) {
myaddr->sa.sa_family = AF_INET;
#ifdef IP_RECVDSTADDR
if (cmptr->cmsg_level == IPPROTO_IP &&
cmptr->cmsg_type == IP_RECVDSTADDR) {
memcpy(&myaddr->sin_addr, CMSG_DATA(cmptr),
sizeof(struct in_addr));
}
if (cmptr->cmsg_level == IPPROTO_IP &&
cmptr->cmsg_type == IP_RECVDSTADDR) {
memcpy(&myaddr->si.sin_addr, CMSG_DATA(cmptr),
sizeof(struct in_addr));
}
#endif
#ifdef IP_PKTINFO
if (cmptr->cmsg_level == IPPROTO_IP &&
cmptr->cmsg_type == IP_PKTINFO) {
memcpy(&pktinfo, CMSG_DATA(cmptr),
sizeof(struct in_pktinfo));
memcpy(&myaddr->sin_addr, &pktinfo.ipi_addr,
sizeof(struct in_addr));
if (cmptr->cmsg_level == IPPROTO_IP &&
cmptr->cmsg_type == IP_PKTINFO) {
memcpy(&pktinfo, CMSG_DATA(cmptr),
sizeof(struct in_pktinfo));
memcpy(&myaddr->si.sin_addr, &pktinfo.ipi_addr,
sizeof(struct in_addr));
}
#endif
}
#ifdef HAVE_IPV6
else if (from->sa_family == AF_INET6) {
myaddr->sa.sa_family = AF_INET6;
#ifdef IP6_RECVDSTADDR
if (cmptr->cmsg_level == IPPROTO_IPV6 &&
cmptr->cmsg_type == IPV6_RECVDSTADDR )
memcpy(&myaddr->s6.sin6_addr, CMSG_DATA(cmptr),
sizeof(struct in6_addr));
#endif
#ifdef HAVE_STRUCT_IN6_PKTINFO
if (cmptr->cmsg_level == IPPROTO_IPV6 &&
(cmptr->cmsg_type == IPV6_RECVPKTINFO ||
cmptr->cmsg_type == IPV6_PKTINFO)) {
memcpy(&pktinfo6, CMSG_DATA(cmptr),
sizeof(struct in6_pktinfo));
memcpy(&myaddr->s6.sin6_addr, &pktinfo6.ipi6_addr,
sizeof(struct in6_addr));
}
#endif
}
#endif
}
/* If the address is not a valid local address,
* then bind to any address...
*/
if (address_is_local(myaddr) != 1) {
if (myaddr->sa.sa_family == AF_INET)
((struct sockaddr_in *)myaddr)->sin_addr.s_addr = INADDR_ANY;
#ifdef HAVE_IPV6
else if (myaddr->sa.sa_family == AF_INET6)
memset(&myaddr->s6.sin6_addr, 0, sizeof(struct in6_addr));
#endif
}
}
/* If the address is not a valid local address, then bind to any address... */
if (address_is_local(myaddr) != 1)
myaddr->sin_addr.s_addr = INADDR_ANY;
return n;
}
@ -181,15 +245,13 @@ myrecvfrom(int s, void *buf, int len, unsigned int flags,
int
myrecvfrom(int s, void *buf, int len, unsigned int flags,
struct sockaddr *from, int *fromlen, struct sockaddr_in *myaddr)
struct sockaddr *from, int *fromlen, union sock_addr *myaddr)
{
/* There is no way we can get the local address, fudge it */
bzero(myaddr, sizeof(struct sockaddr_in));
myaddr->sin_family = AF_INET;
myaddr->sin_port = htons(IPPORT_TFTP);
bzero(&myaddr->sin_addr, sizeof(myaddr->sin_addr));
bzero(myaddr, sizeof(*myaddr));
myaddr->sa.sa_family = from->sa_family;
sa_set_port(myaddr, htons(IPPORT_TFTP));
return recvfrom(s, buf, len, flags, from, fromlen);
}

View file

@ -19,5 +19,5 @@
int
myrecvfrom(int s, void *buf, int len, unsigned int flags,
struct sockaddr *from, socklen_t * fromlen,
struct sockaddr_in *myaddr);
struct sockaddr *from, socklen_t *fromlen,
union sock_addr *myaddr);

View file

@ -40,7 +40,7 @@
.I directory...
.SH DESCRIPTION
.B tftpd
is a server for the IPv4 Trivial File Transfer Protocol. The TFTP
is a server for the Trivial File Transfer Protocol. The TFTP
protocol is extensively used to support remote booting of diskless
devices. The server is normally started by
.BR inetd ,
@ -48,6 +48,12 @@ but can also run standalone.
.PP
.SH OPTIONS
.TP
.B \-4
Connect with IPv4 only, if IPv6 support was compiled in.
.TP
.B \-6
Connect with IPv6 only, if IPv6 support was compiled in.
.TP
.B \-l
Run the server in standalone (listen) mode, rather than run from
.BR inetd .

View file

@ -42,7 +42,6 @@
#include <sys/ioctl.h>
#include <signal.h>
#include <netdb.h>
#include <ctype.h>
#include <pwd.h>
#include <limits.h>
@ -65,6 +64,12 @@ int allow_severity = -1; /* Don't log at all */
struct request_info wrap_request;
#endif
#ifdef HAVE_IPV6
int ai_fam = AF_UNSPEC;
#else
int ai_fam = AF_INET;
#endif
#define TIMEOUT 1000000 /* Default timeout (us) */
#define TRIES 6 /* Number of attempts to send each packet */
#define TIMEOUT_LIMIT ((1 << TRIES)-1)
@ -82,7 +87,9 @@ char buf[PKTSIZE];
char ackbuf[PKTSIZE];
unsigned int max_blksize = MAX_SEGSIZE;
struct sockaddr_in from;
char tmpbuf[INET6_ADDRSTRLEN], *tmp_p;
union sock_addr from;
socklen_t fromlen;
off_t tsize;
int tsize_ok;
@ -254,10 +261,16 @@ int main(int argc, char **argv)
struct tftphdr *tp;
struct passwd *pw;
struct options *opt;
struct sockaddr_in myaddr;
struct sockaddr_in bindaddr;
union sock_addr myaddr;
struct sockaddr_in bindaddr4;
#ifdef HAVE_IPV6
struct sockaddr_in6 bindaddr6;
#endif
int n;
int fd = 0;
int fd = -1;
int fd4 = -1;
int fd6 = -1;
int fdmax = 0;
int standalone = 0; /* Standalone (listen) mode */
int nodaemon = 0; /* Do not detach process */
char *address = NULL; /* Address to listen to */
@ -283,8 +296,16 @@ int main(int argc, char **argv)
srand(time(NULL) ^ getpid());
while ((c = getopt(argc, argv, "cspvVlLa:B:u:U:r:t:T:R:m:")) != -1)
while ((c = getopt(argc, argv, "46cspvVlLa:B:u:U:r:t:T:R:m:")) != -1)
switch (c) {
#ifdef HAVE_IPV6
case '4':
ai_fam = AF_INET;
break;
case '6':
ai_fam = AF_INET6;
break;
#endif
case 'c':
cancreate = 1;
break;
@ -417,12 +438,6 @@ int main(int argc, char **argv)
if (spec_umask || !unixperms)
umask(my_umask);
/* Note: on Cygwin, select() on a nonblocking socket becomes
a nonblocking select. */
#ifndef __CYGWIN__
set_socket_nonblock(fd, 1);
#endif
#ifdef WITH_REGEX
if (rewrite_file)
rewrite_rules = read_remap_rules(rewrite_file);
@ -430,16 +445,45 @@ int main(int argc, char **argv)
/* If we're running standalone, set up the input port */
if (standalone) {
fd = socket(PF_INET, SOCK_DGRAM, 0);
memset(&bindaddr, 0, sizeof bindaddr);
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = INADDR_ANY;
bindaddr.sin_port = htons(IPPORT_TFTP);
#ifdef HAVE_IPV6
if (ai_fam != AF_INET6) {
#endif
fd4 = socket(AF_INET, SOCK_DGRAM, 0);
if (fd4 < 0) {
syslog(LOG_ERR, "cannot open IPv4 socket: %m");
exit(EX_OSERR);
}
#ifndef __CYGWIN__
set_socket_nonblock(fd4, 1);
#endif
memset(&bindaddr4, 0, sizeof bindaddr4);
bindaddr4.sin_family = AF_INET;
bindaddr4.sin_addr.s_addr = INADDR_ANY;
bindaddr4.sin_port = htons(IPPORT_TFTP);
#ifdef HAVE_IPV6
}
if (ai_fam != AF_INET) {
fd6 = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd6 < 0) {
if (fd4 < 0) {
syslog(LOG_ERR, "cannot open IPv6 socket: %m");
exit(EX_OSERR);
} else {
syslog(LOG_ERR,
"cannot open IPv6 socket, disable IPv6: %m");
}
}
#ifndef __CYGWIN__
set_socket_nonblock(fd6, 1);
#endif
memset(&bindaddr6, 0, sizeof bindaddr6);
bindaddr6.sin6_family = AF_INET6;
bindaddr6.sin6_port = htons(IPPORT_TFTP);
}
#endif
if (address) {
char *portptr, *eportptr;
struct hostent *hostent;
int err;
struct servent *servent;
unsigned long port;
@ -447,17 +491,40 @@ int main(int argc, char **argv)
portptr = strrchr(address, ':');
if (portptr)
*portptr++ = '\0';
else
portptr = (char *)"tftp";
if (*address) {
hostent = gethostbyname(address);
if (!hostent || hostent->h_addrtype != AF_INET) {
syslog(LOG_ERR,
"cannot resolve local bind address: %s",
address);
exit(EX_NOINPUT);
if (fd4 >= 0) {
bindaddr4.sin_family = AF_INET;
err = set_sock_addr(address,
(union sock_addr *)&bindaddr4, NULL);
if (err) {
syslog(LOG_ERR,
"cannot resolve local IPv4 bind address: %s",
address);
exit(EX_NOINPUT);
}
}
memcpy(&bindaddr.sin_addr, hostent->h_addr,
hostent->h_length);
#ifdef HAVE_IPV6
if (fd6 >= 0) {
err = set_sock_addr(address,
(union sock_addr *)&bindaddr6, NULL);
if (err) {
if (fd4 >= 0) {
syslog(LOG_ERR,
"cannot resolve local IPv6 bind address: %s"
"; using IPv4 only", address);
close(fd6);
fd6 = -1;
} else {
syslog(LOG_ERR,
"cannot resolve local IPv6 bind address: %s",
address);
exit(EX_NOINPUT);
}
}
}
#endif
} else {
/* Default to using INADDR_ANY */
}
@ -465,10 +532,20 @@ int main(int argc, char **argv)
if (portptr && *portptr) {
servent = getservbyname(portptr, "udp");
if (servent) {
bindaddr.sin_port = servent->s_port;
if (fd4 >= 0)
bindaddr4.sin_port = servent->s_port;
#ifdef HAVE_IPV6
if (fd6 >= 0)
bindaddr6.sin6_port = servent->s_port;
#endif
} else if ((port = strtoul(portptr, &eportptr, 0))
&& !*eportptr) {
bindaddr.sin_port = htons(port);
if (fd4 >= 0)
bindaddr4.sin_port = htons(port);
#ifdef HAVE_IPV6
if (fd6 >= 0)
bindaddr6.sin6_port = htons(port);
#endif
} else if (!strcmp(portptr, "tftp")) {
/* It's TFTP, we're OK */
} else {
@ -479,11 +556,37 @@ int main(int argc, char **argv)
}
}
if (bind(fd, (struct sockaddr *)&bindaddr, sizeof bindaddr) < 0) {
syslog(LOG_ERR, "cannot bind to local socket: %m");
exit(EX_OSERR);
if (fd4 >= 0) {
if (bind(fd4, (struct sockaddr *)&bindaddr4,
sizeof(bindaddr4)) < 0) {
syslog(LOG_ERR, "cannot bind to local IPv4 socket: %m");
exit(EX_OSERR);
}
}
#ifdef HAVE_IPV6
if (fd6 >= 0) {
int on = 1;
#if defined(IPV6_V6ONLY)
if (setsockopt(fd6, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&on,
sizeof(on))) {
syslog(LOG_ERR, "cannot setsockopt IPV6_V6ONLY %m");
}
#endif
if (bind(fd6, (struct sockaddr *)&bindaddr6,
sizeof(bindaddr6)) < 0) {
if (fd4 >= 0) {
syslog(LOG_ERR,
"cannot bind to local IPv6 socket,"
"IPv6 disabled: %m");
close(fd6);
fd6 = -1;
} else {
syslog(LOG_ERR, "cannot bind to local IPv6 socket: %m");
exit(EX_OSERR);
}
}
}
#endif
/* Daemonize this process */
/* Note: when running in secure mode (-s), we must not chroot, since
we are already in the proper directory. */
@ -491,10 +594,21 @@ int main(int argc, char **argv)
syslog(LOG_ERR, "cannot daemonize: %m");
exit(EX_OSERR);
}
if (fd6 > fd4)
fdmax = fd6;
else
fdmax = fd4;
} else {
/* 0 is our socket descriptor */
close(1);
close(2);
fd = 0;
fdmax = 0;
/* Note: on Cygwin, select() on a nonblocking socket becomes
a nonblocking select. */
#ifndef __CYGWIN__
set_socket_nonblock(fd, 1);
#endif
}
/* Disable path MTU discovery */
@ -533,29 +647,61 @@ int main(int argc, char **argv)
}
FD_ZERO(&readset);
FD_SET(fd, &readset);
if (standalone) {
if (fd4 >= 0) {
FD_SET(fd4, &readset);
#ifdef __CYGWIN__
/* On Cygwin, select() on a nonblocking socket returns
immediately, with a rv of 0! */
set_socket_nonblock(fd4, 0);
#endif
}
if (fd6 >= 0) {
FD_SET(fd6, &readset);
#ifdef __CYGWIN__
/* On Cygwin, select() on a nonblocking socket returns
immediately, with a rv of 0! */
set_socket_nonblock(fd6, 0);
#endif
}
} else { /* fd always 0 */
fd = 0;
#ifdef __CYGWIN__
/* On Cygwin, select() on a nonblocking socket returns
immediately, with a rv of 0! */
set_socket_nonblock(fd, 0);
#endif
FD_SET(fd, &readset);
}
tv_waittime.tv_sec = waittime;
tv_waittime.tv_usec = 0;
#ifdef __CYGWIN__
/* On Cygwin, select() on a nonblocking socket returns immediately,
with a rv of 0! */
set_socket_nonblock(fd, 0);
#endif
/* Never time out if we're in standalone mode */
rv = select(fd + 1, &readset, NULL, NULL,
rv = select(fdmax + 1, &readset, NULL, NULL,
standalone ? NULL : &tv_waittime);
if (rv == -1 && errno == EINTR)
continue; /* Signal caught, reloop */
if (rv == -1) {
syslog(LOG_ERR, "select loop: %m");
exit(EX_IOERR);
} else if (rv == 0) {
exit(0); /* Timeout, return to inetd */
}
if (standalone) {
if ((fd4 >= 0) && FD_ISSET(fd4, &readset))
fd = fd4;
else if ((fd6 >= 0) && FD_ISSET(fd6, &readset))
fd = fd6;
else /* not in set ??? */
continue;
}
#ifdef __CYGWIN__
set_socket_nonblock(fd, 1);
/* On Cygwin, select() on a nonblocking socket returns
immediately, with a rv of 0! */
set_socket_nonblock(fd, 0);
#endif
fromlen = sizeof(from);
@ -570,18 +716,32 @@ int main(int argc, char **argv)
exit(EX_IOERR);
}
}
if (from.sin_family != AF_INET) {
syslog(LOG_ERR,
"received address was not AF_INET, please check your inetd config");
#ifdef HAVE_IPV6
if ((from.sa.sa_family != AF_INET) && (from.sa.sa_family != AF_INET6)) {
syslog(LOG_ERR, "received address was not AF_INET/AF_INET6,"
" please check your inetd config");
#else
if (from.sa.sa_family != AF_INET) {
syslog(LOG_ERR, "received address was not AF_INET,"
" please check your inetd config");
#endif
exit(EX_PROTOCOL);
}
if (standalone && myaddr.sin_addr.s_addr == INADDR_ANY) {
/* myrecvfrom() didn't capture the source address; but we might
have bound to a specific address, if so we should use it */
memcpy(&myaddr.sin_addr, &bindaddr.sin_addr,
sizeof bindaddr.sin_addr);
if (standalone) {
if ((from.sa.sa_family == AF_INET) &&
(myaddr.si.sin_addr.s_addr == INADDR_ANY)) {
/* myrecvfrom() didn't capture the source address; but we might
have bound to a specific address, if so we should use it */
memcpy(SOCKADDR_P(&myaddr), &bindaddr4.sin_addr,
sizeof(bindaddr4.sin_addr));
#ifdef HAVE_IPV6
} else if ((from.sa.sa_family == AF_INET6) &&
IN6_IS_ADDR_UNSPECIFIED(SOCKADDR_P(&myaddr))) {
memcpy(SOCKADDR_P(&myaddr), &bindaddr6.sin6_addr,
sizeof(bindaddr6.sin6_addr));
#endif
}
}
/*
@ -609,14 +769,19 @@ int main(int argc, char **argv)
RQ_FILE, fd,
RQ_CLIENT_SIN, &from, RQ_SERVER_SIN, &myaddr, 0);
sock_methods(&wrap_request);
tmp_p = (char *)inet_ntop(myaddr.sa.sa_family, SOCKADDR_P(&myaddr),
tmpbuf, INET6_ADDRSTRLEN);
if (!tmp_p) {
tmp_p = tmpbuf;
strcpy(tmpbuf, "???");
}
if (hosts_access(&wrap_request) == 0) {
if (deny_severity != -1)
syslog(deny_severity, "connection refused from %s",
inet_ntoa(from.sin_addr));
syslog(deny_severity, "connection refused from %s", tmp_p);
exit(EX_NOPERM); /* Access denied */
} else if (allow_severity != -1) {
syslog(allow_severity, "connect from %s",
inet_ntoa(from.sin_addr));
syslog(allow_severity, "connect from %s", tmp_p);
}
#endif
@ -626,7 +791,7 @@ int main(int argc, char **argv)
/* Get a socket. This has to be done before the chroot(), since
some systems require access to /dev to create a socket. */
peer = socket(AF_INET, SOCK_DGRAM, 0);
peer = socket(myaddr.sa.sa_family, SOCK_DGRAM, 0);
if (peer < 0) {
syslog(LOG_ERR, "socket: %m");
exit(EX_IOERR);
@ -677,16 +842,13 @@ int main(int argc, char **argv)
exit(EX_OSERR);
}
/* Other basic setup */
from.sin_family = AF_INET;
/* Process the request... */
if (pick_port_bind(peer, &myaddr, portrange_from, portrange_to) < 0) {
syslog(LOG_ERR, "bind: %m");
exit(EX_IOERR);
}
if (connect(peer, (struct sockaddr *)&from, sizeof from) < 0) {
if (connect(peer, &from.sa, SOCKLEN(&from)) < 0) {
syslog(LOG_ERR, "connect: %m");
exit(EX_IOERR);
}
@ -776,16 +938,22 @@ int tftp(struct tftphdr *tp, int size)
exit(0);
}
if (verbosity >= 1) {
tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from),
tmpbuf, INET6_ADDRSTRLEN);
if (!tmp_p) {
tmp_p = tmpbuf;
strcpy(tmpbuf, "???");
}
if (filename == origfilename
|| !strcmp(filename, origfilename))
syslog(LOG_NOTICE, "%s from %s filename %s\n",
tp_opcode == WRQ ? "WRQ" : "RRQ",
inet_ntoa(from.sin_addr), filename);
tmp_p, filename);
else
syslog(LOG_NOTICE,
"%s from %s filename %s remapped to %s\n",
tp_opcode == WRQ ? "WRQ" : "RRQ",
inet_ntoa(from.sin_addr), origfilename,
tmp_p, origfilename,
filename);
}
ecode =
@ -998,20 +1166,41 @@ int rewrite_macros(char macro, char *output);
int rewrite_macros(char macro, char *output)
{
char *p;
char *p, tb[INET6_ADDRSTRLEN];
int l=0;
switch (macro) {
case 'i':
p = inet_ntoa(from.sin_addr);
if (output)
p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from),
tb, INET6_ADDRSTRLEN);
if (output && p)
strcpy(output, p);
return strlen(p);
if (!p)
return 0;
else
return strlen(p);
case 'x':
if (output)
sprintf(output, "%08lX",
(unsigned long)ntohl(from.sin_addr.s_addr));
return 8;
if (output) {
if (from.sa.sa_family == AF_INET) {
sprintf(output, "%08lX",
(unsigned long)ntohl(from.si.sin_addr.s_addr));
l = 8;
#ifdef HAVE_IPV6
} else {
unsigned char *c = (unsigned char *)SOCKADDR_P(&from);
p = tb;
for (l = 0; l < 16; l++) {
sprintf(p, "%02X", *c);
c++;
p += 2;
}
strcpy(output, tb);
l = strlen(tb);
#endif
}
}
return l;
default:
return -1;
@ -1398,8 +1587,14 @@ static void nak(int error, const char *msg)
length += 4; /* Add space for header */
if (verbosity >= 2) {
tmp_p = (char *)inet_ntop(from.sa.sa_family, SOCKADDR_P(&from),
tmpbuf, INET6_ADDRSTRLEN);
if (!tmp_p) {
tmp_p = tmpbuf;
strcpy(tmpbuf, "???");
}
syslog(LOG_INFO, "sending NAK (%d, %s) to %s",
error, tp->th_msg, inet_ntoa(from.sin_addr));
error, tp->th_msg, tmp_p);
}
if (send(peer, buf, length, 0) != length)