IPv6 support for tftp/tftpd.

Obtained from:	KAME
MFC after:	2 weeks
This commit is contained in:
ume 2002-04-11 17:14:22 +00:00
parent f4a53b8799
commit 3f4c4118b5
5 changed files with 214 additions and 108 deletions

View File

@ -88,10 +88,11 @@ int max_rexmtval = 2*TIMEOUT;
#define PKTSIZE SEGSIZE+4
char buf[PKTSIZE];
char ackbuf[PKTSIZE];
struct sockaddr_in from;
struct sockaddr_storage from;
int fromlen;
void tftp(struct tftphdr *, int);
static void unmappedaddr(struct sockaddr_in6 *);
/*
* Null-terminated directory prefix list for absolute pathname requests and
@ -119,7 +120,8 @@ main(int argc, char *argv[])
struct tftphdr *tp;
int n;
int ch, on;
struct sockaddr_in sin;
struct sockaddr_storage me;
int len;
char *chroot_dir = NULL;
struct passwd *nobody;
char *chuser = "nobody";
@ -244,9 +246,15 @@ main(int argc, char *argv[])
char *tempchroot;
struct stat sb;
int statret;
struct sockaddr_storage ss;
char hbuf[NI_MAXHOST];
tempchroot = inet_ntoa(from.sin_addr);
asprintf(&tempchroot, "%s/%s", chroot_dir, tempchroot);
memcpy(&ss, &from, from.ss_len);
unmappedaddr((struct sockaddr_in6 *)&ss);
getnameinfo((struct sockaddr *)&ss, ss.ss_len,
hbuf, sizeof(hbuf), NULL, 0,
NI_NUMERICHOST | NI_WITHSCOPEID);
asprintf(&tempchroot, "%s/%s", chroot_dir, hbuf);
statret = stat(tempchroot, &sb);
if ((sb.st_mode & S_IFDIR) &&
(statret == 0 || (statret == -1 && ipchroot == 1)))
@ -266,22 +274,37 @@ main(int argc, char *argv[])
setgroups(1, &nobody->pw_gid);
}
from.sin_family = AF_INET;
len = sizeof(me);
if (getsockname(0, (struct sockaddr *)&me, &len) == 0) {
switch (me.ss_family) {
case AF_INET:
((struct sockaddr_in *)&me)->sin_port = 0;
break;
case AF_INET6:
((struct sockaddr_in6 *)&me)->sin6_port = 0;
break;
default:
/* unsupported */
break;
}
} else {
memset(&me, 0, sizeof(me));
me.ss_family = from.ss_family;
me.ss_len = from.ss_len;
}
alarm(0);
close(0);
close(1);
peer = socket(AF_INET, SOCK_DGRAM, 0);
peer = socket(from.ss_family, SOCK_DGRAM, 0);
if (peer < 0) {
syslog(LOG_ERR, "socket: %m");
exit(1);
}
memset(&sin, 0, sizeof(sin));
sin.sin_family = AF_INET;
if (bind(peer, (struct sockaddr *)&sin, sizeof (sin)) < 0) {
if (bind(peer, (struct sockaddr *)&me, me.ss_len) < 0) {
syslog(LOG_ERR, "bind: %m");
exit(1);
}
if (connect(peer, (struct sockaddr *)&from, sizeof(from)) < 0) {
if (connect(peer, (struct sockaddr *)&from, from.ss_len) < 0) {
syslog(LOG_ERR, "connect: %m");
exit(1);
}
@ -406,11 +429,12 @@ tftp(struct tftphdr *tp, int size)
if (has_options)
oack();
if (logging) {
char host[MAXHOSTNAMELEN];
char hbuf[NI_MAXHOST];
realhostname(host, sizeof(host) - 1, &from.sin_addr);
host[sizeof(host) - 1] = '\0';
syslog(LOG_INFO, "%s: %s request for %s: %s", host,
getnameinfo((struct sockaddr *)&from, from.ss_len,
hbuf, sizeof(hbuf), NULL, 0,
NI_WITHSCOPEID);
syslog(LOG_INFO, "%s: %s request for %s: %s", hbuf,
tp->th_opcode == WRQ ? "write" : "read",
filename, errtomsg(ecode));
}
@ -766,6 +790,27 @@ nak(int error)
syslog(LOG_ERR, "nak: %m");
}
/* translate IPv4 mapped IPv6 address to IPv4 address */
static void
unmappedaddr(struct sockaddr_in6 *sin6)
{
struct sockaddr_in *sin4;
u_int32_t addr;
int port;
if (sin6->sin6_family != AF_INET6 ||
!IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr))
return;
sin4 = (struct sockaddr_in *)sin6;
addr = *(u_int32_t *)&sin6->sin6_addr.s6_addr[12];
port = sin6->sin6_port;
memset(sin4, 0, sizeof(struct sockaddr_in));
sin4->sin_addr.s_addr = addr;
sin4->sin_port = port;
sin4->sin_family = AF_INET;
sin4->sin_len = sizeof(struct sockaddr_in);
}
/*
* Send an oack packet (option acknowledgement).
*/

View File

@ -77,9 +77,8 @@ __FBSDID("$FreeBSD$");
#define MAXLINE 200
#define TIMEOUT 5 /* secs between rexmt's */
struct sockaddr_in peeraddr;
struct sockaddr_storage peeraddr;
int f;
short port;
int trace;
int verbose;
int connected;
@ -88,7 +87,6 @@ char line[MAXLINE];
int margc;
char *margv[20];
jmp_buf toplevel;
struct servent *sp;
void get(int, char **);
void help(int, char **);
@ -98,6 +96,7 @@ void put(int, char **);
void quit(int, char **);
void setascii(int, char **);
void setbinary(int, char **);
void setpeer0(char *, char *);
void setpeer(int, char **);
void setrexmt(int, char **);
void settimeout(int, char **);
@ -160,18 +159,7 @@ main(argc, argv)
int argc;
char *argv[];
{
struct sockaddr_in lsin;
sp = getservbyname("tftp", "udp");
if (sp == 0)
errx(1, "udp/tftp: unknown service");
f = socket(AF_INET, SOCK_DGRAM, 0);
if (f < 0)
err(3, "socket");
bzero((char *)&lsin, sizeof(lsin));
lsin.sin_family = AF_INET;
if (bind(f, (struct sockaddr *)&lsin, sizeof(lsin)) < 0)
err(1, "bind");
f = -1;
strcpy(mode, "netascii");
signal(SIGINT, intr);
if (argc > 1) {
@ -186,12 +174,79 @@ main(argc, argv)
char hostname[MAXHOSTNAMELEN];
void
setpeer0(host, port)
char *host;
char *port;
{
struct addrinfo hints, *res0, *res;
int error;
struct sockaddr_storage ss;
char *cause = "unknown";
if (connected) {
close(f);
f = -1;
}
connected = 0;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
hints.ai_protocol = IPPROTO_UDP;
hints.ai_flags = AI_CANONNAME;
if (!port)
port = "tftp";
error = getaddrinfo(host, port, &hints, &res0);
if (error) {
warnx("%s", gai_strerror(error));
return;
}
for (res = res0; res; res = res->ai_next) {
if (res->ai_addrlen > sizeof(peeraddr))
continue;
f = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if (f < 0) {
cause = "socket";
continue;
}
memset(&ss, 0, sizeof(ss));
ss.ss_family = res->ai_family;
ss.ss_len = res->ai_addrlen;
if (bind(f, (struct sockaddr *)&ss, ss.ss_len) < 0) {
cause = "bind";
close(f);
f = -1;
continue;
}
break;
}
if (f < 0)
warn("%s", cause);
else {
/* res->ai_addr <= sizeof(peeraddr) is guaranteed */
memcpy(&peeraddr, res->ai_addr, res->ai_addrlen);
if (res->ai_canonname) {
(void) strncpy(hostname, res->ai_canonname,
sizeof(hostname));
} else
(void) strncpy(hostname, host, sizeof(hostname));
hostname[sizeof(hostname)-1] = 0;
connected = 1;
}
freeaddrinfo(res0);
}
void
setpeer(argc, argv)
int argc;
char *argv[];
{
struct hostent *host;
if (argc < 2) {
strcpy(line, "Connect ");
@ -205,34 +260,10 @@ setpeer(argc, argv)
printf("usage: %s host-name [port]\n", argv[0]);
return;
}
host = gethostbyname(argv[1]);
if (host) {
peeraddr.sin_family = host->h_addrtype;
bcopy(host->h_addr, &peeraddr.sin_addr,
MIN(sizeof(peeraddr.sin_addr), (size_t)host->h_length));
strncpy(hostname, host->h_name, sizeof(hostname));
} else {
peeraddr.sin_family = AF_INET;
peeraddr.sin_addr.s_addr = inet_addr(argv[1]);
if (peeraddr.sin_addr.s_addr == INADDR_NONE) {
connected = 0;
printf("%s: unknown host\n", argv[1]);
return;
}
strncpy(hostname, argv[1], sizeof(hostname));
}
hostname[sizeof(hostname) - 1] = '\0';
port = sp->s_port;
if (argc == 3) {
port = atoi(argv[2]);
if (port < 0) {
printf("%s: bad port number\n", argv[2]);
connected = 0;
return;
}
port = htons(port);
}
connected = 1;
if (argc == 3)
setpeer0(argv[1], NULL);
else
setpeer0(argv[1], argv[2]);
}
struct modes {
@ -336,9 +367,8 @@ put(argc, argv)
return;
}
targ = argv[argc - 1];
if (index(argv[argc - 1], ':')) {
if (rindex(argv[argc - 1], ':')) {
char *lcp;
struct hostent *hp;
for (n = 1; n < argc - 1; n++)
if (index(argv[n], ':')) {
@ -346,20 +376,13 @@ put(argc, argv)
return;
}
lcp = argv[argc - 1];
targ = index(lcp, ':');
targ = rindex(lcp, ':');
*targ++ = 0;
hp = gethostbyname(lcp);
if (hp == NULL) {
fprintf(stderr, "tftp: %s: ", lcp);
herror((char *)NULL);
return;
if (lcp[0] == '[' && lcp[strlen(lcp) - 1] == ']') {
lcp[strlen(lcp) - 1] = '\0';
lcp++;
}
bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr,
MIN(sizeof(peeraddr.sin_addr), (size_t)hp->h_length));
peeraddr.sin_family = hp->h_addrtype;
connected = 1;
strncpy(hostname, hp->h_name, sizeof(hostname));
hostname[sizeof(hostname) - 1] = '\0';
setpeer0(lcp, NULL);
}
if (!connected) {
printf("No target machine specified.\n");
@ -375,7 +398,6 @@ put(argc, argv)
if (verbose)
printf("putting %s to %s:%s [%s]\n",
cp, hostname, targ, mode);
peeraddr.sin_port = port;
xmitfile(fd, targ, mode);
return;
}
@ -393,7 +415,6 @@ put(argc, argv)
if (verbose)
printf("putting %s to %s:%s [%s]\n",
argv[n], hostname, targ, mode);
peeraddr.sin_port = port;
xmitfile(fd, targ, mode);
}
}
@ -433,31 +454,27 @@ get(argc, argv)
}
if (!connected) {
for (n = 1; n < argc ; n++)
if (index(argv[n], ':') == 0) {
if (rindex(argv[n], ':') == 0) {
getusage(argv[0]);
return;
}
}
for (n = 1; n < argc ; n++) {
src = index(argv[n], ':');
src = rindex(argv[n], ':');
if (src == NULL)
src = argv[n];
else {
struct hostent *hp;
char *lcp;
*src++ = 0;
hp = gethostbyname(argv[n]);
if (hp == NULL) {
fprintf(stderr, "tftp: %s: ", argv[n]);
herror((char *)NULL);
continue;
lcp = argv[n];
if (lcp[0] == '[' && lcp[strlen(lcp) - 1] == ']') {
lcp[strlen(lcp) - 1] = '\0';
lcp++;
}
bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr,
MIN(sizeof(peeraddr.sin_addr), (size_t)hp->h_length));
peeraddr.sin_family = hp->h_addrtype;
connected = 1;
strncpy(hostname, hp->h_name, sizeof(hostname));
hostname[sizeof(hostname) - 1] = '\0';
setpeer0(lcp, NULL);
if (!connected)
continue;
}
if (argc < 4) {
cp = argc == 3 ? argv[2] : tail(src);
@ -469,7 +486,6 @@ get(argc, argv)
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode);
peeraddr.sin_port = port;
recvfile(fd, src, mode);
break;
}
@ -482,7 +498,6 @@ get(argc, argv)
if (verbose)
printf("getting from %s:%s to %s [%s]\n",
hostname, src, cp, mode);
peeraddr.sin_port = port;
recvfile(fd, src, mode);
}
}

View File

@ -1,4 +1,4 @@
.\" Copyright (c) 1990, 1993, 1994
.\" $NetBSD: tftp.1,v 1.11 1999/12/13 04:44:55 itojun Exp $
.\" The Regents of the University of California. All rights reserved.
.\"
.\" Redistribution and use in source and binary forms, with or without
@ -137,6 +137,11 @@ If the remote-directory form is used, the remote host is
assumed to be a
.Tn UNIX
machine.
If you need to specify IPv6 numeric address to
.Ar hosts ,
wrap them using square bracket like
.Ar [hosts]:filename
to disambiguate the colon.
.Pp
.It Cm quit
Exit

View File

@ -61,11 +61,12 @@ __FBSDID("$FreeBSD$");
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include "extern.h"
#include "tftpsubs.h"
extern struct sockaddr_in peeraddr; /* filled in by main */
extern struct sockaddr_storage peeraddr; /* filled in by main */
extern int f; /* the opened socket */
extern int trace;
extern int verbose;
@ -78,13 +79,14 @@ int timeout;
jmp_buf toplevel;
jmp_buf timeoutbuf;
static void nak(int);
static void nak(int, struct sockaddr *);
static int makerequest(int, const char *, struct tftphdr *, const char *);
static void printstats(const char *, unsigned long);
static void startclock(void);
static void stopclock(void);
static void timer(int);
static void tpacket(const char *, struct tftphdr *, int);
static int cmpport(struct sockaddr *, struct sockaddr *);
/*
* Send the requested file.
@ -101,9 +103,11 @@ xmitfile(fd, name, mode)
volatile unsigned short block;
volatile int size, convert;
volatile unsigned long amount;
struct sockaddr_in from;
struct sockaddr_storage from;
int fromlen;
FILE *file;
struct sockaddr_storage peer;
struct sockaddr_storage serv; /* valid server port number */
startclock(); /* start stat's clock */
dp = r_init(); /* reset fillbuf/read-ahead code */
@ -112,6 +116,8 @@ xmitfile(fd, name, mode)
convert = !strcmp(mode, "netascii");
block = 0;
amount = 0;
memcpy(&peer, &peeraddr, peeraddr.ss_len);
memset(&serv, 0, sizeof(serv));
signal(SIGALRM, timer);
do {
@ -121,7 +127,7 @@ xmitfile(fd, name, mode)
/* size = read(fd, dp->th_data, SEGSIZE); */
size = readit(file, &dp, convert);
if (size < 0) {
nak(errno + 100);
nak(errno + 100, (struct sockaddr *)&peer);
break;
}
dp->th_opcode = htons((u_short)DATA);
@ -133,7 +139,7 @@ xmitfile(fd, name, mode)
if (trace)
tpacket("sent", dp, size + 4);
n = sendto(f, dp, size + 4, 0,
(struct sockaddr *)&peeraddr, sizeof(peeraddr));
(struct sockaddr *)&peer, peer.ss_len);
if (n != size + 4) {
warn("sendto");
goto abort;
@ -151,7 +157,14 @@ xmitfile(fd, name, mode)
warn("recvfrom");
goto abort;
}
peeraddr.sin_port = from.sin_port; /* added */
if (!serv.ss_family)
serv = from;
else if (!cmpport((struct sockaddr *)&serv,
(struct sockaddr *)&from)) {
warn("server port mismatch");
goto abort;
}
peer = from;
if (trace)
tpacket("received", ap, n);
/* should verify packet came from server */
@ -207,10 +220,12 @@ recvfile(fd, name, mode)
volatile unsigned short block;
volatile int size, firsttrip;
volatile unsigned long amount;
struct sockaddr_in from;
struct sockaddr_storage from;
int fromlen;
FILE *file;
volatile int convert; /* true if converting crlf -> lf */
struct sockaddr_storage peer;
struct sockaddr_storage serv; /* valid server port number */
startclock();
dp = w_init();
@ -220,6 +235,8 @@ recvfile(fd, name, mode)
block = 1;
firsttrip = 1;
amount = 0;
memcpy(&peer, &peeraddr, peeraddr.ss_len);
memset(&serv, 0, sizeof(serv));
signal(SIGALRM, timer);
do {
@ -237,8 +254,8 @@ recvfile(fd, name, 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, (struct sockaddr *)&peer,
peer.ss_len) != size) {
alarm(0);
warn("sendto");
goto abort;
@ -256,7 +273,14 @@ recvfile(fd, name, mode)
warn("recvfrom");
goto abort;
}
peeraddr.sin_port = from.sin_port; /* added */
if (!serv.ss_family)
serv = from;
else if (!cmpport((struct sockaddr *)&serv,
(struct sockaddr *)&from)) {
warn("server port mismatch");
goto abort;
}
peer = from;
if (trace)
tpacket("received", dp, n);
/* should verify client address */
@ -288,7 +312,7 @@ recvfile(fd, name, mode)
/* size = write(fd, dp->th_data, n - 4); */
size = writeit(file, &dp, n - 4, convert);
if (size < 0) {
nak(errno + 100);
nak(errno + 100, (struct sockaddr *)&peer);
break;
}
amount += size;
@ -296,8 +320,8 @@ recvfile(fd, name, mode)
abort: /* ok to ack, since user */
ap->th_opcode = htons((u_short)ACK); /* has seen err msg */
ap->th_block = htons((u_short)block);
(void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peeraddr,
sizeof(peeraddr));
(void) sendto(f, ackbuf, 4, 0, (struct sockaddr *)&peer,
peer.ss_len);
write_behind(file, convert); /* flush last buffer */
fclose(file);
stopclock();
@ -347,8 +371,9 @@ struct errmsg {
* offset by 100.
*/
static void
nak(error)
nak(error, peer)
int error;
struct sockaddr *peer;
{
struct errmsg *pe;
struct tftphdr *tp;
@ -368,8 +393,7 @@ nak(error)
length = strlen(pe->e_msg) + 4;
if (trace)
tpacket("sent", tp, length);
if (sendto(f, ackbuf, length, 0, (struct sockaddr *)&peeraddr,
sizeof(peeraddr)) != length)
if (sendto(f, ackbuf, length, 0, peer, peer->sa_len) != length)
warn("nak");
}
@ -457,3 +481,20 @@ timer(sig)
}
longjmp(timeoutbuf, 1);
}
static int
cmpport(sa, sb)
struct sockaddr *sa;
struct sockaddr *sb;
{
char a[NI_MAXSERV], b[NI_MAXSERV];
if (getnameinfo(sa, sa->sa_len, NULL, 0, a, sizeof(a), NI_NUMERICSERV))
return 0;
if (getnameinfo(sb, sb->sa_len, NULL, 0, b, sizeof(b), NI_NUMERICSERV))
return 0;
if (strcmp(a, b) != 0)
return 0;
return 1;
}

View File

@ -260,7 +260,7 @@ synchnet(f)
{
int i, j = 0;
char rbuf[PKTSIZE];
struct sockaddr_in from;
struct sockaddr_storage from;
int fromlen;
while (1) {