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);

Reply via email to