From 3f4c4118b54d662d6326c43ad14a9a9472efcc58 Mon Sep 17 00:00:00 2001 From: ume Date: Thu, 11 Apr 2002 17:14:22 +0000 Subject: [PATCH] IPv6 support for tftp/tftpd. Obtained from: KAME MFC after: 2 weeks --- libexec/tftpd/tftpd.c | 73 ++++++++++++++---- usr.bin/tftp/main.c | 167 ++++++++++++++++++++++------------------ usr.bin/tftp/tftp.1 | 7 +- usr.bin/tftp/tftp.c | 73 ++++++++++++++---- usr.bin/tftp/tftpsubs.c | 2 +- 5 files changed, 214 insertions(+), 108 deletions(-) diff --git a/libexec/tftpd/tftpd.c b/libexec/tftpd/tftpd.c index 057a3bd21662..9176f410b3fa 100644 --- a/libexec/tftpd/tftpd.c +++ b/libexec/tftpd/tftpd.c @@ -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). */ diff --git a/usr.bin/tftp/main.c b/usr.bin/tftp/main.c index 38b376cc4cfd..251abf50a277 100644 --- a/usr.bin/tftp/main.c +++ b/usr.bin/tftp/main.c @@ -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); } } diff --git a/usr.bin/tftp/tftp.1 b/usr.bin/tftp/tftp.1 index a09cf37885d9..7f2159039813 100644 --- a/usr.bin/tftp/tftp.1 +++ b/usr.bin/tftp/tftp.1 @@ -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 diff --git a/usr.bin/tftp/tftp.c b/usr.bin/tftp/tftp.c index 7b4eb95032ca..b2cc8683b7b7 100644 --- a/usr.bin/tftp/tftp.c +++ b/usr.bin/tftp/tftp.c @@ -61,11 +61,12 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #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; +} diff --git a/usr.bin/tftp/tftpsubs.c b/usr.bin/tftp/tftpsubs.c index 50ff82d6bb2b..a3673d83c721 100644 --- a/usr.bin/tftp/tftpsubs.c +++ b/usr.bin/tftp/tftpsubs.c @@ -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) {