On Fri, Apr 27, 2012 at 12:34:19PM -0400, Brad Smith wrote: > Tested against OpenBSD's tftpd and dnsmasq. Working fine for me.
here is an updated version of the diff. - setpeer0() and setpeer() were renamed accordingly for clarity and consistency. (requested by henning@) - fix memleak(call freeaddrinfo() to release *res0) - checks against valid server port number - man page tweaked based on points by sthen@ and jmc@ this diff has been OK by sthen@ jmc@ henning@ i will commit it tomorrow if no objections till there. ? 1 ? tftp_ipv6_3.diff Index: main.c =================================================================== RCS file: /cvs/src/usr.bin/tftp/main.c,v retrieving revision 1.30 diff -u -p -r1.30 main.c --- main.c 27 Oct 2009 23:59:44 -0000 1.30 +++ main.c 24 Apr 2012 15:39:19 -0000 @@ -68,7 +68,8 @@ void put(int, char **); void quit(int, char **); void setascii(int, char **); void setbinary(int, char **); -void setpeer(int, char **); +void setpeer(char *, char *); +void parsearg(int, char **); void setrexmt(int, char **); void settimeout(int, char **); void settrace(int, char **); @@ -86,9 +87,8 @@ static __dead void command(void); struct cmd *getcmd(char *); char *tail(char *); -struct sockaddr_in peeraddr; +struct sockaddr_storage peeraddr; int f; -short port; int trace; int verbose; int connected; @@ -98,7 +98,6 @@ int margc; char *margv[MAXARGV+1]; char *prompt = "tftp"; void intr(int); -struct servent *sp; int rexmtval = TIMEOUT; int maxtimeout = 5 * TIMEOUT; char hostname[MAXHOSTNAMELEN]; @@ -134,7 +133,7 @@ struct cmd { }; struct cmd cmdtab[] = { - { "connect", chelp, setpeer }, + { "connect", chelp, parsearg }, { "mode", mhelp, modecmd }, { "put", shelp, put }, { "get", rhelp, get }, @@ -170,26 +169,14 @@ struct modes { int main(int argc, char *argv[]) { - struct sockaddr_in s_in; - - /* socket, bind */ - 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 *)&s_in, sizeof(s_in)); - s_in.sin_family = AF_INET; - if (bind(f, (struct sockaddr *)&s_in, sizeof(s_in)) < 0) - err(1, "bind"); + f = -1; /* set default transfer mode */ strlcpy(mode, "netascii", sizeof(mode)); /* set peer if given */ if (argc > 1) - setpeer(argc, argv); + parsearg(argc, argv); /* catch SIGINT */ signal(SIGINT, intr); @@ -205,11 +192,73 @@ main(int argc, char *argv[]) } void -setpeer(int argc, char *argv[]) +setpeer(char *host, char *port) { - struct hostent *host; - const char *errstr; + 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 +parsearg(int argc, char *argv[]) +{ if (argc < 2) { strlcpy(line, "Connect ", sizeof(line)); printf("(to) "); @@ -223,32 +272,10 @@ setpeer(int argc, char *argv[]) printf("usage: %s [host [port]]\n", argv[0]); return; } - if (inet_aton(argv[1], &peeraddr.sin_addr) != 0) { - peeraddr.sin_family = AF_INET; - (void)strncpy(hostname, argv[1], sizeof(hostname)); - hostname[sizeof(hostname) - 1] = '\0'; - } else { - host = gethostbyname(argv[1]); - if (host == 0) { - 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); - (void)strlcpy(hostname, host->h_name, sizeof(hostname)); - } - port = sp->s_port; - if (argc == 3) { - port = strtonum(argv[2], 1, 65535, &errstr); - if (errstr) { - printf("%s: port number is %s\n", argv[2], errstr); - connected = 0; - return; - } - port = htons(port); - } - connected = 1; + if (argc == 2) + setpeer(argv[1], NULL); + else + setpeer(argv[1], argv[2]); } void @@ -331,8 +358,7 @@ put(int argc, char *argv[]) return; } targ = argv[argc - 1]; - if (strchr(argv[argc - 1], ':')) { - struct hostent *hp; + if (strrchr(argv[argc - 1], ':')) { for (n = 1; n < argc - 1; n++) if (strchr(argv[n], ':')) { @@ -340,18 +366,13 @@ put(int argc, char *argv[]) return; } cp = argv[argc - 1]; - targ = strchr(cp, ':'); + targ = strrchr(cp, ':'); *targ++ = 0; - hp = gethostbyname(cp); - if (hp == NULL) { - warnx("%s: %s", cp, hstrerror(h_errno)); - return; + if (cp[0] == '[' && cp[strlen(cp) - 1] == ']') { + cp[strlen(cp) - 1] = '\0'; + cp++; } - bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr, hp->h_length); - peeraddr.sin_family = hp->h_addrtype; - connected = 1; - port = sp->s_port; - strlcpy(hostname, hp->h_name, sizeof(hostname)); + setpeer(cp, NULL); } if (!connected) { printf("No target machine specified.\n"); @@ -367,7 +388,6 @@ put(int argc, char *argv[]) if (verbose) printf("putting %s to %s:%s [%s]\n", cp, hostname, targ, mode); - peeraddr.sin_port = port; sendfile(fd, targ, mode); return; } @@ -388,7 +408,6 @@ put(int argc, char *argv[]) if (verbose) printf("putting %s to %s:%s [%s]\n", argv[n], hostname, cp, mode); - peeraddr.sin_port = port; sendfile(fd, cp, mode); free(cp); } @@ -428,29 +447,27 @@ get(int argc, char *argv[]) } if (!connected) { for (n = 1; n < argc; n++) - if (strchr(argv[n], ':') == 0) { + if (strrchr(argv[n], ':') == 0) { getusage(argv[0]); return; } } for (n = 1; n < argc; n++) { - src = strchr(argv[n], ':'); + src = strrchr(argv[n], ':'); if (src == NULL) src = argv[n]; else { - struct hostent *hp; + char *cp; *src++ = 0; - hp = gethostbyname(argv[n]); - if (hp == NULL) { - warnx("%s: %s", argv[n], hstrerror(h_errno)); - continue; + cp = argv[n]; + if (cp[0] == '[' && cp[strlen(cp) - 1] == ']') { + cp[strlen(cp) - 1] = '\0'; + cp++; } - bcopy(hp->h_addr, (caddr_t)&peeraddr.sin_addr, - hp->h_length); - peeraddr.sin_family = hp->h_addrtype; - connected = 1; - strlcpy(hostname, hp->h_name, sizeof(hostname)); + setpeer(cp, NULL); + if (!connected) + continue; } if (argc < 4) { cp = argc == 3 ? argv[2] : tail(src); @@ -462,7 +479,6 @@ get(int argc, char *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; } @@ -475,7 +491,6 @@ get(int argc, char *argv[]) if (verbose) printf("getting from %s:%s to %s [%s]\n", hostname, src, cp, mode); - peeraddr.sin_port = port; recvfile(fd, src, mode); } } Index: tftp.1 =================================================================== RCS file: /cvs/src/usr.bin/tftp/tftp.1,v retrieving revision 1.19 diff -u -p -r1.19 tftp.1 --- tftp.1 1 Mar 2012 03:47:19 -0000 1.19 +++ tftp.1 24 Apr 2012 15:39:19 -0000 @@ -119,6 +119,9 @@ When using the argument, the .Ar host will be used as the default host for future transfers. +IPv6 addresses can be specified by enclosing +.Ar host +in square brackets. If .Ar localname is specified, @@ -156,6 +159,9 @@ When using the argument, the .Ar host will be used as the default host for future transfers. +IPv6 addresses can be specified by enclosing +.Ar host +in square brackets. If .Ar remotename is specified, the file is stored remotely as Index: tftp.c =================================================================== RCS file: /cvs/src/usr.bin/tftp/tftp.c,v retrieving revision 1.22 diff -u -p -r1.22 tftp.c --- tftp.c 27 Oct 2009 23:59:44 -0000 1.22 +++ tftp.c 24 Apr 2012 15:39:21 -0000 @@ -53,12 +53,14 @@ #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <netdb.h> #include "extern.h" #include "tftpsubs.h" +static int cmpport(struct sockaddr *, struct sockaddr *); static int makerequest(int, const char *, struct tftphdr *, const char *); -static void nak(int); +static void nak(int, struct sockaddr *); static void tpacket(const char *, struct tftphdr *, int); static void startclock(void); static void stopclock(void); @@ -67,7 +69,7 @@ static void printtimeout(void); static void oack(struct tftphdr *, int, int); static int oack_set(const char *, const char *); -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; @@ -124,7 +126,8 @@ void sendfile(int fd, char *name, char *mode) { struct tftphdr *dp, *ap; /* data and ack packets */ - struct sockaddr_in from; + struct sockaddr_storage from, peer; + struct sockaddr_storage serv; /* valid server port number */ struct pollfd pfd[1]; unsigned long amount; socklen_t fromlen; @@ -138,6 +141,8 @@ sendfile(int fd, char *name, char *mode) convert = !strcmp(mode, "netascii"); block = 0; amount = 0; + memcpy(&peer, &peeraddr, peeraddr.ss_len); + memset(&serv, 0, sizeof(serv)); do { /* read data from file */ @@ -146,7 +151,7 @@ sendfile(int fd, char *name, char *mode) else { size = readit(file, &dp, convert, segment_size); if (size < 0) { - nak(errno + 100); + nak(errno + 100, (struct sockaddr *)&peer); break; } dp->th_opcode = htons((u_short)DATA); @@ -164,8 +169,8 @@ sendfile(int fd, char *name, char *mode) if (trace) tpacket("sent", dp, size + 4); if (sendto(f, dp, size + 4, 0, - (struct sockaddr *)&peeraddr, - sizeof(peeraddr)) != size + 4) { + (struct sockaddr *)&peer, + peer.ss_len) != size + 4) { warn("sendto"); goto abort; } @@ -202,7 +207,14 @@ sendfile(int fd, char *name, char *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); @@ -256,7 +268,8 @@ void recvfile(int fd, char *name, char *mode) { struct tftphdr *dp, *ap; /* data and ack packets */ - struct sockaddr_in from; + struct sockaddr_storage from, peer; + struct sockaddr_storage serv; /* valid server port number */ struct pollfd pfd[1]; unsigned long amount; socklen_t fromlen; @@ -273,6 +286,8 @@ recvfile(int fd, char *name, char *mode) block = 1; amount = 0; firsttrip = 1; + memcpy(&peer, &peeraddr, peeraddr.ss_len); + memset(&serv, 0, sizeof(serv)); options: do { @@ -298,8 +313,8 @@ options: if (trace) tpacket("sent", ap, size); if (sendto(f, ackbuf, size, 0, - (struct sockaddr *)&peeraddr, - sizeof(peeraddr)) != size) { + (struct sockaddr *)&peer, + peer.ss_len) != size) { warn("sendto"); goto abort; } @@ -335,7 +350,14 @@ options: 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); @@ -371,7 +393,7 @@ options: /* write data to file */ size = writeit(file, &dp, n - 4, convert); if (size < 0) { - nak(errno + 100); + nak(errno + 100, (struct sockaddr *)&peer); break; } amount += size; @@ -381,8 +403,8 @@ abort: /* ok to ack, since user has seen err msg */ ap->th_opcode = htons((u_short)ACK); 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); @@ -395,6 +417,20 @@ abort: } static int +cmpport(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); +} + +static int makerequest(int request, const char *name, struct tftphdr *tp, const char *mode) { @@ -436,7 +472,7 @@ makerequest(int request, const char *nam * offset by 100. */ static void -nak(int error) +nak(int error, struct sockaddr *peer) { struct errmsg *pe; struct tftphdr *tp; @@ -457,8 +493,8 @@ nak(int error) length = packet_size; 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"); } @@ -588,6 +624,8 @@ oack_set(const char *option, const char { int i, n; const char *errstr; + struct sockaddr_storage peer; + memcpy(&peer, &peeraddr, peeraddr.ss_len); for (i = 0; options[i].o_type != NULL; i++) { if (!strcasecmp(options[i].o_type, option)) { @@ -600,7 +638,7 @@ oack_set(const char *option, const char &errstr); if (errstr || rexmtval != n || opt_tout == 0) { - nak(EOPTNEG); + nak(EOPTNEG, (struct sockaddr *)&peer); intrflag = 1; return (-1); } @@ -612,7 +650,7 @@ oack_set(const char *option, const char &errstr); if (errstr || opt_blksize != n || opt_blksize == 0) { - nak(EOPTNEG); + nak(EOPTNEG, (struct sockaddr *)&peer); intrflag = 1; return (-1); } Index: tftpsubs.c =================================================================== RCS file: /cvs/src/usr.bin/tftp/tftpsubs.c,v retrieving revision 1.14 diff -u -p -r1.14 tftpsubs.c --- tftpsubs.c 27 Oct 2009 23:59:44 -0000 1.14 +++ tftpsubs.c 24 Apr 2012 15:39:21 -0000 @@ -258,7 +258,7 @@ synchnet(int f) { int i, j = 0; char rbuf[SEGSIZE_MIN]; - struct sockaddr_in from; + struct sockaddr_storage from; socklen_t fromlen; for (;;) {