On Wed, Apr 18, 2012 at 11:58:26PM -0400, Lawrence Teo wrote: > This diff adds a -s flag to ftp(1) to let the user specify the > source IP address of the connection. This is useful when using > ftp(1) over VPN tunnels or when an alternate source IP is required > to fetch a file from a FTP/HTTP/HTTPS server due to access control > policies.
I have revised this diff based on feedback from haesbaert@. The primary change is to use AI_NUMERICHOST in the hints addrinfo struct that's passed to getaddrinfo(). The resulting ftp(1) binary still passes all 48 tests from my test script at http://lteo.net/stuff/ftp-test Comments and more testing are welcome! Thanks, Lawrence Index: fetch.c =================================================================== RCS file: /cvs/src/usr.bin/ftp/fetch.c,v retrieving revision 1.104 diff -u -p -r1.104 fetch.c --- fetch.c 23 Apr 2012 21:22:02 -0000 1.104 +++ fetch.c 25 Apr 2012 02:12:13 -0000 @@ -179,7 +179,7 @@ url_get(const char *origline, const char char *hosttail, *cause = "unknown", *newline, *host, *port, *buf = NULL; char *epath, *redirurl, *loctail; int error, i, isftpurl = 0, isfileurl = 0, isredirect = 0, rval = -1; - struct addrinfo hints, *res0, *res; + struct addrinfo hints, *res0, *res, *ares = NULL; const char * volatile savefile; char * volatile proxyurl = NULL; char *cookie = NULL; @@ -198,6 +198,7 @@ url_get(const char *origline, const char #endif /* !SMALL */ SSL *ssl = NULL; int status; + int save_errno; direction = "received"; @@ -490,6 +491,17 @@ noslash: goto cleanup_url_get; } +#ifndef SMALL + if (srcaddr) { + hints.ai_flags |= AI_NUMERICHOST; + error = getaddrinfo(srcaddr, NULL, &hints, &ares); + if (error) { + warnx("%s: %s", gai_strerror(error), srcaddr); + goto cleanup_url_get; + } + } +#endif /* !SMALL */ + s = -1; for (res = res0; res; res = res->ai_next) { if (getnameinfo(res->ai_addr, res->ai_addrlen, hbuf, @@ -504,10 +516,28 @@ noslash: continue; } +#ifndef SMALL + if (srcaddr) { + if (ares->ai_family != res->ai_family) { + close(s); + s = -1; + errno = EINVAL; + cause = "bind"; + continue; + } + if (bind(s, ares->ai_addr, ares->ai_addrlen) < 0) { + save_errno = errno; + close(s); + errno = save_errno; + s = -1; + cause = "bind"; + continue; + } + } +#endif /* !SMALL */ + again: if (connect(s, res->ai_addr, res->ai_addrlen) < 0) { - int save_errno; - if (errno == EINTR) goto again; save_errno = errno; @@ -532,6 +562,10 @@ again: break; } freeaddrinfo(res0); +#ifndef SMALL + if (srcaddr) + freeaddrinfo(ares); +#endif /* !SMALL */ if (s < 0) { warn("%s", cause); goto cleanup_url_get; Index: ftp.1 =================================================================== RCS file: /cvs/src/usr.bin/ftp/ftp.1,v retrieving revision 1.81 diff -u -p -r1.81 ftp.1 --- ftp.1 26 Jul 2010 21:31:34 -0000 1.81 +++ ftp.1 25 Apr 2012 02:12:13 -0000 @@ -42,10 +42,12 @@ .Op Fl k Ar seconds .Op Fl P Ar port .Op Fl r Ar seconds +.Op Fl s Ar srcaddr .Op Ar host Op Ar port .Nm ftp .Op Fl C .Op Fl o Ar output +.Op Fl s Ar srcaddr .Sm off .No ftp:// Oo Ar user : password No @ .Oc Ar host Oo : Ar port @@ -57,6 +59,7 @@ .Op Fl C .Op Fl c Ar cookie .Op Fl o Ar output +.Op Fl s Ar srcaddr .Sm off .No http:// Ar host Oo : Ar port .Oc No / Ar file @@ -66,6 +69,7 @@ .Op Fl C .Op Fl c Ar cookie .Op Fl o Ar output +.Op Fl s Ar srcaddr .Sm off .No https:// Ar host Oo : Ar port .Oc No / Ar file @@ -74,6 +78,7 @@ .Nm ftp .Op Fl C .Op Fl o Ar output +.Op Fl s Ar srcaddr .Sm off .No file: Ar file .Sm on @@ -81,6 +86,7 @@ .Nm ftp .Op Fl C .Op Fl o Ar output +.Op Fl s Ar srcaddr .Sm off .Ar host : No / Ar file Oo / .Oc @@ -220,6 +226,12 @@ if the server does not support passive c .It Fl r Ar seconds Retry to connect if failed, pausing for number of .Ar seconds . +.It Fl s Ar srcaddr +Use +.Ar srcaddr +on the local machine as the source address +of the connection. +Only useful on systems with more than one address. .It Fl t Enables packet tracing. .It Fl V Index: ftp.c =================================================================== RCS file: /cvs/src/usr.bin/ftp/ftp.c,v retrieving revision 1.81 diff -u -p -r1.81 ftp.c --- ftp.c 3 Sep 2010 03:49:37 -0000 1.81 +++ ftp.c 25 Apr 2012 02:12:14 -0000 @@ -114,7 +114,7 @@ hookup(char *host, char *port) { int s, tos, error; static char hostnamebuf[MAXHOSTNAMELEN]; - struct addrinfo hints, *res, *res0; + struct addrinfo hints, *res, *res0, *ares; char hbuf[NI_MAXHOST]; char *cause = "unknown"; socklen_t namelen; @@ -163,6 +163,25 @@ hookup(char *host, char *port) else strlcpy(hostnamebuf, host, sizeof(hostnamebuf)); hostname = hostnamebuf; + +#ifndef SMALL + if (srcaddr) { + struct addrinfo ahints; + + memset(&ahints, 0, sizeof(ahints)); + ahints.ai_family = family; + ahints.ai_socktype = SOCK_STREAM; + ahints.ai_flags |= AI_NUMERICHOST; + ahints.ai_protocol = 0; + + error = getaddrinfo(srcaddr, NULL, &ahints, &ares); + if (error) { + warnx("%s: %s", gai_strerror(error), srcaddr); + code = -1; + return (0); + } + } +#endif /* !SMALL */ s = -1; for (res = res0; res; res = res->ai_next) { @@ -183,6 +202,25 @@ hookup(char *host, char *port) cause = "socket"; continue; } +#ifndef SMALL + if (srcaddr) { + if (ares->ai_family != res->ai_family) { + close(s); + s = -1; + errno = EINVAL; + cause = "bind"; + continue; + } + if (bind(s, ares->ai_addr, ares->ai_addrlen) < 0) { + cause = "bind"; + error = errno; + close(s); + errno = error; + s = -1; + continue; + } + } +#endif /* !SMALL */ while ((error = connect(s, res->ai_addr, res->ai_addrlen)) < 0 && errno == EINTR) { ; @@ -218,6 +256,12 @@ hookup(char *host, char *port) namelen = res->ai_addrlen; freeaddrinfo(res0); res0 = res = NULL; +#ifndef SMALL + if (srcaddr) { + freeaddrinfo(ares); + ares = NULL; + } +#endif /* !SMALL */ if (getsockname(s, (struct sockaddr *)&myctladdr, &namelen) < 0) { warn("getsockname"); code = -1; @@ -1240,12 +1284,31 @@ initconn(void) u_int af, hal, pal; char *pasvcmd = NULL; socklen_t namelen; + struct addrinfo *ares; if (myctladdr.su_family == AF_INET6 && (IN6_IS_ADDR_LINKLOCAL(&myctladdr.su_sin6.sin6_addr) || IN6_IS_ADDR_SITELOCAL(&myctladdr.su_sin6.sin6_addr))) { warnx("use of scoped address can be troublesome"); } +#ifndef SMALL + if (srcaddr) { + struct addrinfo ahints; + + memset(&ahints, 0, sizeof(ahints)); + ahints.ai_family = family; + ahints.ai_socktype = SOCK_STREAM; + ahints.ai_flags |= AI_NUMERICHOST; + ahints.ai_protocol = 0; + + error = getaddrinfo(srcaddr, NULL, &ahints, &ares); + if (error) { + warnx("%s: %s", gai_strerror(error), srcaddr); + code = -1; + return (0); + } + } +#endif /* !SMALL */ reinit: if (passivemode) { data_addr = myctladdr; @@ -1255,6 +1318,13 @@ reinit: return (1); } #ifndef SMALL + if (srcaddr) { + if (bind(data, ares->ai_addr, ares->ai_addrlen) < 0) { + warn("bind"); + close(data); + return (1); + } + } if ((options & SO_DEBUG) && setsockopt(data, SOL_SOCKET, SO_DEBUG, (char *)&on, sizeof(on)) < 0) Index: ftp_var.h =================================================================== RCS file: /cvs/src/usr.bin/ftp/ftp_var.h,v retrieving revision 1.31 diff -u -p -r1.31 ftp_var.h --- ftp_var.h 2 Jul 2010 22:01:10 -0000 1.31 +++ ftp_var.h 25 Apr 2012 02:12:14 -0000 @@ -165,6 +165,7 @@ size_t cursor_argc; /* location of cu size_t cursor_argo; /* offset of cursor in margv[cursor_argc] */ char *cookiefile; /* cookie jar to use */ int resume; /* continue transfer */ +char *srcaddr; /* source address to bind to */ #endif /* !SMALL */ off_t bytes; /* current # of bytes read */ Index: main.c =================================================================== RCS file: /cvs/src/usr.bin/ftp/main.c,v retrieving revision 1.81 diff -u -p -r1.81 main.c --- main.c 29 Jun 2010 23:12:33 -0000 1.81 +++ main.c 25 Apr 2012 02:12:14 -0000 @@ -114,6 +114,7 @@ main(volatile int argc, char *argv[]) hist = NULL; cookiefile = NULL; resume = 0; + srcaddr = NULL; marg_sl = sl_init(); #endif /* !SMALL */ mark = HASHBYTES; @@ -174,7 +175,7 @@ main(volatile int argc, char *argv[]) cookiefile = getenv("http_cookies"); #endif /* !SMALL */ - while ((ch = getopt(argc, argv, "46AaCc:dEegik:mno:pP:r:tvV")) != -1) { + while ((ch = getopt(argc, argv, "46AaCc:dEegik:mno:pP:r:s:tvV")) != -1) { switch (ch) { case '4': family = PF_INET; @@ -275,6 +276,12 @@ main(volatile int argc, char *argv[]) } break; + case 's': +#ifndef SMALL + srcaddr = optarg; +#endif /* !SMALL */ + break; + case 't': trace = 1; break; @@ -756,15 +763,21 @@ usage(void) " %s [-C] " #endif /* !SMALL */ "[-o output] " +#ifndef SMALL + "[-s srcaddr] " +#endif /* !SMALL */ "ftp://[user:password@]host[:port]/file[/] ...\n" " %s " #ifndef SMALL "[-C] [-c cookie] " #endif /* !SMALL */ "[-o output] " +#ifndef SMALL + "[-s srcaddr] " +#endif /* !SMALL */ "http://host[:port]/file ...\n" #ifndef SMALL - " %s [-C] [-c cookie] [-o output] " + " %s [-C] [-c cookie] [-o output] [-s srcaddr] " "https://host[:port]/file ...\n" #endif /* !SMALL */ " %s " @@ -772,12 +785,19 @@ usage(void) "[-C] " #endif /* !SMALL */ "[-o output] " +#ifndef SMALL + "[-s srcaddr] " +#endif /* !SMALL */ "file:file ...\n" " %s " #ifndef SMALL "[-C] " #endif /* !SMALL */ - "[-o output] host:/file[/] ...\n", + "[-o output] " +#ifndef SMALL + "[-s srcaddr] " +#endif /* !SMALL */ + "host:/file[/] ...\n", #ifndef SMALL __progname, __progname, __progname, __progname, __progname, __progname);