From 28f22b6591c3bca4d6ead5f943107ddfe0d6705c Mon Sep 17 00:00:00 2001 From: Karsten Keil Date: Wed, 23 Jul 2008 18:32:21 +0200 Subject: [PATCH] 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 Signed-off-by: H. Peter Anvin --- common/tftpsubs.c | 44 +++++- common/tftpsubs.h | 54 +++++++- tftp/main.c | 135 +++++++++++------- tftp/tftp.1.in | 7 +- tftp/tftp.c | 28 ++-- tftpd/recvfrom.c | 140 +++++++++++++------ tftpd/recvfrom.h | 4 +- tftpd/tftpd.8.in | 8 +- tftpd/tftpd.c | 339 ++++++++++++++++++++++++++++++++++++---------- 9 files changed, 574 insertions(+), 185 deletions(-) diff --git a/common/tftpsubs.c b/common/tftpsubs.c index ef05cc4..45f4907 100644 --- a/common/tftpsubs.c +++ b/common/tftpsubs.c @@ -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; +} diff --git a/common/tftpsubs.h b/common/tftpsubs.h index a5c88e3..20cf47e 100644 --- a/common/tftpsubs.h +++ b/common/tftpsubs.h @@ -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 diff --git a/tftp/main.c b/tftp/main.c index ec57bc2..6176b37 100644 --- a/tftp/main.c +++ b/tftp/main.c @@ -40,7 +40,6 @@ */ #include #include -#include #ifdef WITH_READLINE #include #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); } } diff --git a/tftp/tftp.1.in b/tftp/tftp.1.in index 8ef1e86..8bbb796 100644 --- a/tftp/tftp.1.in +++ b/tftp/tftp.1.in @@ -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. diff --git a/tftp/tftp.c b/tftp/tftp.c index 2d26b4b..d15da22 100644 --- a/tftp/tftp.c +++ b/tftp/tftp.c @@ -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"); } diff --git a/tftpd/recvfrom.c b/tftpd/recvfrom.c index fee6d03..389ba82 100644 --- a/tftpd/recvfrom.c +++ b/tftpd/recvfrom.c @@ -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 /* 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); } diff --git a/tftpd/recvfrom.h b/tftpd/recvfrom.h index fda65c0..e3c4055 100644 --- a/tftpd/recvfrom.h +++ b/tftpd/recvfrom.h @@ -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); diff --git a/tftpd/tftpd.8.in b/tftpd/tftpd.8.in index 49cf359..b4d1ac3 100644 --- a/tftpd/tftpd.8.in +++ b/tftpd/tftpd.8.in @@ -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 . diff --git a/tftpd/tftpd.c b/tftpd/tftpd.c index b5798a6..38f5e7b 100644 --- a/tftpd/tftpd.c +++ b/tftpd/tftpd.c @@ -42,7 +42,6 @@ #include #include -#include #include #include #include @@ -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)