From 90cb3309da2d5eaa65f5477bd45b30d2d18996db Mon Sep 17 00:00:00 2001 From: hniksic Date: Wed, 29 Oct 2003 10:23:56 -0800 Subject: [PATCH] [svn] Imported Mauro's IPv6 changes. --- src/connect.c | 153 ++++++++----- src/connect.h | 8 +- src/ftp-basic.c | 588 ++++++++++++++++++++++++++++++++++++++---------- src/ftp.c | 117 +++++++++- src/ftp.h | 6 +- src/host.c | 437 ++++++++++++++++++++--------------- src/host.h | 87 +++---- src/http.c | 4 +- 8 files changed, 989 insertions(+), 411 deletions(-) diff --git a/src/connect.c b/src/connect.c index 2fc23235..252cf512 100644 --- a/src/connect.c +++ b/src/connect.c @@ -67,32 +67,44 @@ extern int errno; /* Variables shared by bindport and acceptport: */ static int msock = -1; -static struct sockaddr *addr; +/*static struct sockaddr *addr;*/ -static ip_address bind_address; -static int bind_address_resolved; - -static void -resolve_bind_address (void) +static int +resolve_bind_address (int flags, ip_address *addr) { - struct address_list *al; + struct address_list *al = NULL; + int bind_address_resolved = 0; - if (bind_address_resolved || opt.bind_address == NULL) - /* Nothing to do. */ - return; + if (opt.bind_address != NULL) + { + al = lookup_host (opt.bind_address, flags | LH_SILENT | LH_PASSIVE); + + if (al == NULL) + { + logprintf (LOG_NOTQUIET, + _("Unable to convert `%s' to a bind address. Reverting to ANY.\n"), + opt.bind_address); + } + else + bind_address_resolved = 1; + } - al = lookup_host (opt.bind_address, 1); - if (!al) + if (al == NULL) { - logprintf (LOG_NOTQUIET, - _("Unable to convert `%s' to a bind address. Reverting to ANY.\n"), - opt.bind_address); - return; + const char *unspecified_address = "0.0.0.0"; +#ifdef ENABLE_IPV6 + if (flags & BIND_ON_IPV6_ONLY) + unspecified_address = "::"; +#endif + al = lookup_host (unspecified_address, LH_SILENT | LH_PASSIVE); } - address_list_copy_one (al, 0, &bind_address); + assert (al != NULL); + + address_list_copy_one (al, 0, addr); address_list_release (al); - bind_address_resolved = 1; + + return bind_address_resolved; } struct cwt_context { @@ -151,15 +163,16 @@ set_connection_host_name (const char *host) int connect_to_one (ip_address *addr, unsigned short port, int silent) { - wget_sockaddr sa; + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *)&ss; int sock, save_errno; /* Set port and protocol */ - wget_sockaddr_set_address (&sa, ip_default_family, port, addr); + sockaddr_set_address (sa, port, addr); if (!silent) { - char *pretty_addr = pretty_print_address (addr); + const char *pretty_addr = pretty_print_address (addr); if (connection_host_name && 0 != strcmp (connection_host_name, pretty_addr)) logprintf (LOG_VERBOSE, _("Connecting to %s[%s]:%hu... "), @@ -170,7 +183,7 @@ connect_to_one (ip_address *addr, unsigned short port, int silent) } /* Make an internet socket, stream type. */ - sock = socket (ip_default_family, SOCK_STREAM, 0); + sock = socket (sa->sa_family, SOCK_STREAM, 0); if (sock < 0) goto out; @@ -191,22 +204,26 @@ connect_to_one (ip_address *addr, unsigned short port, int silent) `--post-file', also set SO_SNDBUF here. */ } - resolve_bind_address (); - if (bind_address_resolved) + if (opt.bind_address) { /* Bind the client side to the requested address. */ - wget_sockaddr bsa; - wget_sockaddr_set_address (&bsa, ip_default_family, 0, &bind_address); - if (bind (sock, &bsa.sa, sockaddr_len ())) - { - CLOSE (sock); - sock = -1; - goto out; + ip_address bind_address; + if (resolve_bind_address (0, &bind_address)) + { + struct sockaddr_storage bss; + struct sockaddr *bsa = (struct sockaddr *)&bss; + sockaddr_set_address (bsa, 0, &bind_address); + if (bind (sock, bsa, sockaddr_len (bsa))) + { + CLOSE (sock); + sock = -1; + goto out; + } } } /* Connect the socket to the remote host. */ - if (connect_with_timeout (sock, &sa.sa, sockaddr_len (), + if (connect_with_timeout (sock, sa, sockaddr_len (sa), opt.connect_timeout) < 0) { CLOSE (sock); @@ -297,28 +314,47 @@ test_socket_open (int sock) chosen by the system, and its value is stored to *PORT. The internal variable MPORT is set to the value of the ensuing master socket. Call acceptport() to block for and accept a connection. */ + uerr_t -bindport (unsigned short *port, int family) +bindport (const ip_address *bind_address, unsigned short *port) { - int optval = 1; - wget_sockaddr srv; - memset (&srv, 0, sizeof (wget_sockaddr)); + int family = AF_INET; + int optval; + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *)&ss; + memset (&ss, 0, sizeof (ss)); msock = -1; +#ifdef ENABLE_IPV6 + if (bind_address->type == IPv6_ADDRESS) + family = AF_INET6; +#endif + if ((msock = socket (family, SOCK_STREAM, 0)) < 0) return CONSOCKERR; #ifdef SO_REUSEADDR + optval = 1; if (setsockopt (msock, SOL_SOCKET, SO_REUSEADDR, (char *)&optval, sizeof (optval)) < 0) return CONSOCKERR; #endif - resolve_bind_address (); - wget_sockaddr_set_address (&srv, ip_default_family, htons (*port), - bind_address_resolved ? &bind_address : NULL); - if (bind (msock, &srv.sa, sockaddr_len ()) < 0) +#ifdef ENABLE_IPV6 +# ifdef HAVE_IPV6_V6ONLY + if (family == AF_INET6) + { + optval = 1; + /* if setsockopt fails, go on anyway */ + setsockopt (msock, IPPROTO_IPV6, IPV6_V6ONLY, + (char *)&optval, sizeof (optval)); + } +# endif +#endif + + sockaddr_set_address (sa, htons (*port), bind_address); + if (bind (msock, sa, sockaddr_len (sa)) < 0) { CLOSE (msock); msock = -1; @@ -327,15 +363,16 @@ bindport (unsigned short *port, int family) DEBUGP (("Master socket fd %d bound.\n", msock)); if (!*port) { - socklen_t sa_len = sockaddr_len (); - if (getsockname (msock, &srv.sa, &sa_len) < 0) + socklen_t sa_len = sockaddr_len (sa); + if (getsockname (msock, sa, &sa_len) < 0) { CLOSE (msock); msock = -1; return CONPORTERR; } - *port = wget_sockaddr_get_port (&srv); - DEBUGP (("using port %i.\n", *port)); + *port = sockaddr_get_port (sa); + DEBUGP (("binding to address %s using port %i.\n", + pretty_print_address (bind_address), *port)); } if (listen (msock, 1) < 0) { @@ -389,13 +426,15 @@ select_fd (int fd, double maxtime, int writep) uerr_t acceptport (int *sock) { - socklen_t addrlen = sockaddr_len (); + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *)&ss; + socklen_t addrlen = sizeof (ss); #ifdef HAVE_SELECT if (select_fd (msock, opt.connect_timeout, 0) <= 0) return ACCEPTERR; #endif - if ((*sock = accept (msock, addr, &addrlen)) < 0) + if ((*sock = accept (msock, sa, &addrlen)) < 0) return ACCEPTERR; DEBUGP (("Created socket fd %d.\n", *sock)); return ACCEPTOK; @@ -419,24 +458,34 @@ closeport (int sock) int conaddr (int fd, ip_address *ip) { - wget_sockaddr mysrv; - socklen_t addrlen = sizeof (mysrv); - if (getsockname (fd, &mysrv.sa, &addrlen) < 0) + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *)&ss; + socklen_t addrlen = sizeof (ss); + + if (getsockname (fd, sa, &addrlen) < 0) return 0; - switch (mysrv.sa.sa_family) + switch (sa->sa_family) { #ifdef ENABLE_IPV6 case AF_INET6: - memcpy (ip, &mysrv.sin6.sin6_addr, 16); + ip->type = IPv6_ADDRESS; + ip->addr.ipv6.addr = ((struct sockaddr_in6 *)sa)->sin6_addr; +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + ip->addr.ipv6.scope_id = ((struct sockaddr_in6 *)sa)->sin6_scope_id; +#endif + DEBUGP (("conaddr is: %s\n", pretty_print_address (ip))); return 1; #endif case AF_INET: - map_ipv4_to_ip ((ip4_address *)&mysrv.sin.sin_addr, ip); + ip->type = IPv4_ADDRESS; + ip->addr.ipv4.addr = ((struct sockaddr_in *)sa)->sin_addr; + DEBUGP (("conaddr is: %s\n", pretty_print_address (ip))); return 1; default: abort (); } + return 0; } diff --git a/src/connect.h b/src/connect.h index 75eb9caa..f85fe6a6 100644 --- a/src/connect.h +++ b/src/connect.h @@ -32,6 +32,12 @@ so, delete this exception statement from your version. */ #include "host.h" +/* bindport flags */ +#define BIND_ON_IPV4_ONLY LH_IPv4_ONLY +#ifdef ENABLE_IPV6 +#define BIND_ON_IPV6_ONLY LH_IPv6_ONLY +#endif /* ENABLE_IPV6 */ + /* Function declarations */ int connect_to_one PARAMS ((ip_address *, unsigned short, int)); @@ -40,7 +46,7 @@ void set_connection_host_name PARAMS ((const char *)); int test_socket_open PARAMS ((int)); int select_fd PARAMS ((int, double, int)); -uerr_t bindport PARAMS ((unsigned short *, int)); +uerr_t bindport PARAMS ((const ip_address *, unsigned short *)); uerr_t acceptport PARAMS ((int *)); void closeport PARAMS ((int)); int conaddr PARAMS ((int, ip_address *)); diff --git a/src/ftp-basic.c b/src/ftp-basic.c index 05f3f32e..39d98fd1 100644 --- a/src/ftp-basic.c +++ b/src/ftp-basic.c @@ -29,6 +29,7 @@ so, delete this exception statement from your version. */ #include +#include #include #include #include @@ -126,7 +127,7 @@ ftp_request (const char *command, const char *value) if (strncmp (res, "PASS", 4) != 0) logprintf (LOG_ALWAYS, "--> %s\n", res); else - logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n"); + logputs (LOG_ALWAYS, "--> PASS Turtle Power!\n\n"); } else DEBUGP (("\n--> %s\n", res)); @@ -252,117 +253,246 @@ ftp_login (struct rbuf *rbuf, const char *acc, const char *pass) return FTPOK; } -#ifdef ENABLE_IPV6 +static void +ip_address_to_port_repr (const ip_address *addr, unsigned short port, char *buf, + size_t buflen) +{ + unsigned char *ptr; + + assert (addr != NULL); + assert (addr->type == IPv4_ADDRESS); + assert (buf != NULL); + /* buf must contain the argument of PORT (of the form a,b,c,d,e,f). */ + assert (buflen >= 6 * 4); + + ptr = (unsigned char *)(&addr->addr.ipv4.addr.s_addr); + snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d", ptr[0], ptr[1], + ptr[2], ptr[3], (unsigned) (port & 0xff00) >> 8, port & 0xff); + buf[buflen - 1] = '\0'; +} + +/* Bind a port and send the appropriate PORT command to the FTP + server. Use acceptport after RETR, to get the socket of data + connection. */ uerr_t -ftp_eprt (struct rbuf *rbuf) +ftp_port (struct rbuf *rbuf) { uerr_t err; - char *request, *respline; - ip_address in_addr; + ip_address addr; + int nwritten; unsigned short port; + /* Must contain the argument of PORT (of the form a,b,c,d,e,f). */ + char bytes[6 * 4 + 1]; + + assert (rbuf != NULL); + assert (rbuf_initialized_p (rbuf)); + + /* Get the address of this side of the connection. */ + if (!conaddr (RBUF_FD (rbuf), &addr)) + return BINDERR; - char ipv6 [8 * (4 * 3 + 3) + 8]; - char *bytes; + assert (addr.type == IPv4_ADDRESS); /* Setting port to 0 lets the system choose a free port. */ port = 0; - err = bindport (&port, ip_default_family); - if (err != BINDOK) /* Bind the port. */ + + /* Bind the port. */ + err = bindport (&addr, &port); + if (err != BINDOK) return err; - /* Get the address of this side of the connection. */ - if (!conaddr (RBUF_FD (rbuf), &in_addr)) - /* Huh? This is not BINDERR! */ - return BINDERR; - inet_ntop (AF_INET6, &in_addr, ipv6, sizeof (ipv6)); + /* Construct the argument of PORT (of the form a,b,c,d,e,f). */ + ip_address_to_port_repr (&addr, port, bytes, sizeof (bytes)); - /* Construct the argument of EPRT (of the form |2|IPv6.ascii|PORT.ascii|). */ - bytes = alloca (3 + strlen (ipv6) + 1 + numdigit (port) + 1 + 1); - sprintf (bytes, "|2|%s|%u|", ipv6, port); /* Send PORT request. */ - request = ftp_request ("EPRT", bytes); - if (0 > iwrite (RBUF_FD (rbuf), request, strlen (request))) + request = ftp_request ("PORT", bytes); + nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request)); + if (nwritten < 0) { - closeport (port); xfree (request); + closeport (-1); return WRITEFAILED; } xfree (request); + /* Get appropriate response. */ err = ftp_response (rbuf, &respline); if (err != FTPOK) { - closeport (port); xfree (respline); + closeport (-1); return err; } if (*respline != '2') { - closeport (port); xfree (respline); + closeport (-1); return FTPPORTERR; } xfree (respline); return FTPOK; } -#endif + +#ifdef ENABLE_IPV6 +static void +ip_address_to_lprt_repr (const ip_address *addr, unsigned short port, char *buf, + size_t buflen) +{ + unsigned char *ptr; + + assert (addr != NULL); + assert (addr->type == IPv4_ADDRESS || addr->type == IPv6_ADDRESS); + assert (buf != NULL); + /* buf must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */ + assert (buflen >= 21 * 4); + + /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */ + switch (addr->type) + { + case IPv4_ADDRESS: + ptr = (unsigned char *)(&addr->addr.ipv4.addr); + snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d,%d,%d,%d", 4, 4, + ptr[0], ptr[1], ptr[2], ptr[3], 2, + (unsigned) (port & 0xff00) >> 8, port & 0xff); + buf[buflen - 1] = '\0'; + break; + case IPv6_ADDRESS: + ptr = (unsigned char *)(&addr->addr.ipv6.addr); + snprintf (buf, buflen, "%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d", + 6, 16, ptr[0], ptr[1], ptr[2], ptr[3], ptr[4], ptr[5], ptr[6], ptr[7], + ptr[8], ptr[9], ptr[10], ptr[11], ptr[12], ptr[13], ptr[14], ptr[15], 2, + (unsigned) (port & 0xff00) >> 8, port & 0xff); + buf[buflen - 1] = '\0'; + break; + } +} /* Bind a port and send the appropriate PORT command to the FTP server. Use acceptport after RETR, to get the socket of data connection. */ uerr_t -ftp_port (struct rbuf *rbuf) +ftp_lprt (struct rbuf *rbuf) { uerr_t err; char *request, *respline; - char bytes[6 * 4 +1]; - - ip_address in_addr; - ip4_address in_addr_4; - unsigned char *in_addr4_ptr = (unsigned char *)&in_addr_4; - + ip_address addr; int nwritten; unsigned short port; -#ifdef ENABLE_IPV6 - /* - Only try the Extented Version if we actually use IPv6 - */ - if (ip_default_family == AF_INET6) + /* Must contain the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */ + char bytes[21 * 4 + 1]; + + assert (rbuf != NULL); + assert (rbuf_initialized_p (rbuf)); + + /* Get the address of this side of the connection. */ + if (!conaddr (RBUF_FD (rbuf), &addr)) + return BINDERR; + + assert (addr.type == IPv4_ADDRESS || addr.type == IPv6_ADDRESS); + + /* Setting port to 0 lets the system choose a free port. */ + port = 0; + + /* Bind the port. */ + err = bindport (&addr, &port); + if (err != BINDOK) + return err; + + /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */ + ip_address_to_lprt_repr (&addr, port, bytes, sizeof (bytes)); + + /* Send PORT request. */ + request = ftp_request ("LPRT", bytes); + nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request)); + if (nwritten < 0) { - err = ftp_eprt (rbuf); - if (err == FTPOK) - return err; + xfree (request); + closeport (-1); + return WRITEFAILED; } -#endif + xfree (request); + /* Get appropriate response. */ + err = ftp_response (rbuf, &respline); + if (err != FTPOK) + { + xfree (respline); + closeport (-1); + return err; + } + if (*respline != '2') + { + xfree (respline); + closeport (-1); + return FTPPORTERR; + } + xfree (respline); + return FTPOK; +} + +static void +ip_address_to_eprt_repr (const ip_address *addr, unsigned short port, char *buf, + size_t buflen) +{ + int afnum; + + assert (addr != NULL); + assert (addr->type == IPv4_ADDRESS || addr->type == IPv6_ADDRESS); + assert (buf != NULL); + /* buf must contain the argument of EPRT (of the form |af|addr|port|). + * 4 chars for the | separators, INET6_ADDRSTRLEN chars for addr + * 1 char for af (1-2) and 5 chars for port (0-65535) */ + assert (buflen >= 4 + INET6_ADDRSTRLEN + 1 + 5); + + /* Construct the argument of EPRT (of the form |af|addr|port|). */ + afnum = (addr->type == IPv4_ADDRESS ? 1 : 2); + snprintf (buf, buflen, "|%d|%s|%d|", afnum, pretty_print_address (addr), port); + buf[buflen - 1] = '\0'; +} + +/* Bind a port and send the appropriate PORT command to the FTP + server. Use acceptport after RETR, to get the socket of data + connection. */ +uerr_t +ftp_eprt (struct rbuf *rbuf) +{ + uerr_t err; + char *request, *respline; + ip_address addr; + int nwritten; + unsigned short port; + /* Must contain the argument of EPRT (of the form |af|addr|port|). + * 4 chars for the | separators, ENABLE_IPV6_ADDRSTRLEN chars for addr + * 1 char for af (1-2) and 5 chars for port (0-65535) */ + char bytes[4 + INET6_ADDRSTRLEN + 1 + 5 + 1]; + + assert (rbuf != NULL); + assert (rbuf_initialized_p(rbuf)); + + /* Get the address of this side of the connection. */ + if (!conaddr (RBUF_FD (rbuf), &addr)) + return BINDERR; + + assert (addr.type == IPv4_ADDRESS || addr.type == IPv6_ADDRESS); + /* Setting port to 0 lets the system choose a free port. */ port = 0; - err = bindport (&port, AF_INET); + /* Bind the port. */ + err = bindport (&addr, &port); if (err != BINDOK) return err; - /* Get the address of this side of the connection and convert it - (back) to IPv4. */ - if (!conaddr (RBUF_FD (rbuf), &in_addr)) - /* Huh? This is not BINDERR! */ - return BINDERR; - if (!map_ip_to_ipv4 (&in_addr, &in_addr_4)) - return BINDERR; + /* Construct the argument of LPRT (of the form af,n,h1,h2,...,hn,p1,p2). */ + ip_address_to_eprt_repr (&addr, port, bytes, sizeof (bytes)); - /* Construct the argument of PORT (of the form a,b,c,d,e,f). Port - is unsigned short so (unsigned) (port & 0xff000) >> 8 is the same - like port >> 8 - */ - sprintf (bytes, "%d,%d,%d,%d,%d,%d", - in_addr4_ptr[0], in_addr4_ptr[1], in_addr4_ptr[2], in_addr4_ptr[3], - port >> 8, port & 0xff); /* Send PORT request. */ - request = ftp_request ("PORT", bytes); + request = ftp_request ("EPRT", bytes); nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request)); if (nwritten < 0) { xfree (request); + closeport (-1); return WRITEFAILED; } xfree (request); @@ -371,30 +501,48 @@ ftp_port (struct rbuf *rbuf) if (err != FTPOK) { xfree (respline); + closeport (-1); return err; } if (*respline != '2') { xfree (respline); + closeport (-1); return FTPPORTERR; } xfree (respline); return FTPOK; } +#endif -#ifdef ENABLE_IPV6 +/* Similar to ftp_port, but uses `PASV' to initiate the passive FTP + transfer. Reads the response from server and parses it. Reads the + host and port addresses and returns them. */ uerr_t -ftp_epsv (struct rbuf *rbuf, ip_address *addr, unsigned short *port, - char *typ) +ftp_pasv (struct rbuf *rbuf, ip_address *addr, unsigned short *port) { - int err; - char *s, *respline; - char *request = ftp_request ("EPSV", typ); - if (0 > iwrite (RBUF_FD (rbuf), request, strlen (request))) + char *request, *respline, *s; + int nwritten, i; + uerr_t err; + unsigned char tmp[6]; + + assert (rbuf != NULL); + assert (rbuf_initialized_p(rbuf)); + assert (addr != NULL); + assert (port != NULL); + + memset (addr, 0, sizeof (ip_address)); + + /* Form the request. */ + request = ftp_request ("PASV", NULL); + /* And send it. */ + nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request)); + if (nwritten < 0) { xfree (request); return WRITEFAILED; } + xfree (request); /* Get the server response. */ err = ftp_response (rbuf, &respline); if (err != FTPOK) @@ -409,63 +557,56 @@ ftp_epsv (struct rbuf *rbuf, ip_address *addr, unsigned short *port, } /* Parse the request. */ s = respline; - /* respline::=229 Entering Extended Passive Mode (|||6446|) */ for (s += 4; *s && !ISDIGIT (*s); s++); if (!*s) return FTPINVPASV; - *port=0; - for (; ISDIGIT (*s); s++) - *port = (*s - '0') + 10 * (*port); + for (i = 0; i < 6; i++) + { + tmp[i] = 0; + for (; ISDIGIT (*s); s++) + tmp[i] = (*s - '0') + 10 * tmp[i]; + if (*s == ',') + s++; + else if (i < 5) + { + /* When on the last number, anything can be a terminator. */ + xfree (respline); + return FTPINVPASV; + } + } xfree (respline); - /* Now we have the port but we need the IPv6 :-( */ - { - wget_sockaddr remote; - socklen_t addrlen = sizeof (remote); - struct sockaddr_in *ipv4_sock = (struct sockaddr_in *)&remote; - getpeername (RBUF_FD (rbuf), (struct sockaddr *)&remote, &addrlen); - switch(remote.sa.sa_family) - { - case AF_INET6: - memcpy (addr, &remote.sin6.sin6_addr, 16); - break; - case AF_INET: - map_ipv4_to_ip ((ip4_address *)&ipv4_sock->sin_addr, addr); - break; - default: - abort(); - return FTPINVPASV; - /* realy bad */ - } - } + + addr->type = IPv4_ADDRESS; + /* Mauro Tortonesi: is this safe and/or elegant enough? */ + memcpy (&addr->addr.ipv4.addr, tmp, 4); + *port = ((tmp[4] << 8) & 0xff00) + tmp[5]; + return FTPOK; } -#endif - -/* Similar to ftp_port, but uses `PASV' to initiate the passive FTP +#ifdef ENABLE_IPV6 +/* Similar to ftp_lprt, but uses `LPSV' to initiate the passive FTP transfer. Reads the response from server and parses it. Reads the host and port addresses and returns them. */ uerr_t -ftp_pasv (struct rbuf *rbuf, ip_address *addr, unsigned short *port) +ftp_lpsv (struct rbuf *rbuf, ip_address *addr, unsigned short *port) { char *request, *respline, *s; - int nwritten, i; + int nwritten, i, af, addrlen, portlen; uerr_t err; - unsigned char addr4[4]; + unsigned char tmp[16]; + unsigned char tmpprt[2]; + + assert (rbuf != NULL); + assert (rbuf_initialized_p(rbuf)); + assert (addr != NULL); + assert (port != NULL); + + memset (addr, 0, sizeof (ip_address)); -#ifdef ENABLE_IPV6 - if (ip_default_family == AF_INET6) - { - err = ftp_epsv (rbuf, addr, port, "2"); /* try IPv6 with EPSV */ - if (FTPOK == err) - return FTPOK; - err = ftp_epsv (rbuf, addr, port, "1"); /* try IPv4 with EPSV */ - if (FTPOK == err) - return FTPOK; - } -#endif /* Form the request. */ - request = ftp_request ("PASV", NULL); + request = ftp_request ("LPSV", NULL); + /* And send it. */ nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request)); if (nwritten < 0) @@ -474,6 +615,7 @@ ftp_pasv (struct rbuf *rbuf, ip_address *addr, unsigned short *port) return WRITEFAILED; } xfree (request); + /* Get the server response. */ err = ftp_response (rbuf, &respline); if (err != FTPOK) @@ -485,18 +627,61 @@ ftp_pasv (struct rbuf *rbuf, ip_address *addr, unsigned short *port) { xfree (respline); return FTPNOPASV; - } - /* Parse the request. */ - /* respline::=227 Entering Passive Mode (h1,h2,h3,h4,p1,p2). */ + } + + /* Parse the response. */ s = respline; for (s += 4; *s && !ISDIGIT (*s); s++); if (!*s) return FTPINVPASV; - for (i = 0; i < 4; i++) + + /* First, get the address family */ + af = 0; + for (; ISDIGIT (*s); s++) + af = (*s - '0') + 10 * af; + + if (af != 4 && af != 6) + { + xfree (respline); + return FTPINVPASV; + } + + if (!*s || *s++ != ',') + { + xfree (respline); + return FTPINVPASV; + } + + /* Then, get the address length */ + addrlen = 0; + for (; ISDIGIT (*s); s++) + addrlen = (*s - '0') + 10 * addrlen; + + if (!*s || *s++ != ',') { - addr4[i] = 0; + xfree (respline); + return FTPINVPASV; + } + + if (addrlen > 16) + { + xfree (respline); + return FTPINVPASV; + } + + if ((af == 4 && addrlen != 4) + || (af == 6 && addrlen != 16)) + { + xfree (respline); + return FTPINVPASV; + } + + /* Now, we get the actual address */ + for (i = 0; i < addrlen; i++) + { + tmp[i] = 0; for (; ISDIGIT (*s); s++) - addr4[i] = (*s - '0') + 10 * addr4[i]; + tmp[i] = (*s - '0') + 10 * tmp[i]; if (*s == ',') s++; else @@ -506,29 +691,184 @@ ftp_pasv (struct rbuf *rbuf, ip_address *addr, unsigned short *port) } } - /* Eventually make an IPv4 in IPv6 adress if needed */ - map_ipv4_to_ip ((ip4_address *)addr4, addr); + /* Now, get the port length */ + portlen = 0; + for (; ISDIGIT (*s); s++) + portlen = (*s - '0') + 10 * portlen; - *port=0; + if (!*s || *s++ != ',') + { + xfree (respline); + return FTPINVPASV; + } + + if (portlen > 2) + { + xfree (respline); + return FTPINVPASV; + } + + /* Finally, we get the port number */ + tmpprt[0] = 0; for (; ISDIGIT (*s); s++) - *port = (*s - '0') + 10 * (*port); - if (*s == ',') - s++; + tmpprt[0] = (*s - '0') + 10 * tmpprt[0]; + + if (!*s || *s++ != ',') + { + xfree (respline); + return FTPINVPASV; + } + + tmpprt[1] = 0; + for (; ISDIGIT (*s); s++) + tmpprt[1] = (*s - '0') + 10 * tmpprt[1]; + + assert (s != NULL); + + if (af == 4) + { + addr->type = IPv4_ADDRESS; + memcpy (&addr->addr.ipv4.addr, tmp, 4); + *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1]; + DEBUGP (("lpsv addr is: %s\n", pretty_print_address(addr))); + DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0])); + DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1])); + DEBUGP (("*port is: %d\n", *port)); + } else + { + assert (af == 6); + addr->type = IPv6_ADDRESS; + memcpy (&addr->addr.ipv6.addr, tmp, 16); + *port = ((tmpprt[0] << 8) & 0xff00) + tmpprt[1]; + DEBUGP (("lpsv addr is: %s\n", pretty_print_address(addr))); + DEBUGP (("tmpprt[0] is: %d\n", tmpprt[0])); + DEBUGP (("tmpprt[1] is: %d\n", tmpprt[1])); + DEBUGP (("*port is: %d\n", *port)); + } + + xfree (respline); + return FTPOK; +} + +/* Similar to ftp_eprt, but uses `EPSV' to initiate the passive FTP + transfer. Reads the response from server and parses it. Reads the + host and port addresses and returns them. */ +uerr_t +ftp_epsv (struct rbuf *rbuf, ip_address *addr, unsigned short *port) +{ + char *request, *respline, *start, delim, *s; + int nwritten, i; + uerr_t err; + unsigned short tport; + socklen_t addrlen; + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *)&ss; + + assert (rbuf != NULL); + assert (rbuf_initialized_p(rbuf)); + assert (addr != NULL); + assert (port != NULL); + + addrlen = sizeof (ss); + if (getpeername (rbuf->fd, sa, &addrlen) < 0) + /* Mauro Tortonesi: HOW DO WE HANDLE THIS ERROR? */ + return CONPORTERR; + + assert (sa->sa_family == AF_INET || sa->sa_family == AF_INET6); + + sockaddr_get_address (sa, NULL, addr); + + /* Form the request. */ + /* EPSV 1 means that we ask for IPv4 and EPSV 2 means that we ask for IPv6. */ + request = ftp_request ("EPSV", (sa->sa_family == AF_INET ? "1" : "2")); + + /* And send it. */ + nwritten = iwrite (RBUF_FD (rbuf), request, strlen (request)); + if (nwritten < 0) + { + xfree (request); + return WRITEFAILED; + } + xfree (request); + + /* Get the server response. */ + err = ftp_response (rbuf, &respline); + if (err != FTPOK) + { + xfree (respline); + return err; + } + if (*respline != '2') + { + xfree (respline); + return FTPNOPASV; + } + + assert (respline != NULL); + + DEBUGP(("respline is %s\n", respline)); + + /* Parse the response. */ + s = respline; + + /* Skip the useless stuff and get what's inside the parentheses */ + start = strchr (respline, '('); + if (start == NULL) + { + xfree (respline); + return FTPINVPASV; + } + + /* Skip the first two void fields */ + s = start + 1; + delim = *s++; + if (delim < 33 || delim > 126) { xfree (respline); return FTPINVPASV; + } + + for (i = 0; i < 2; i++) + { + if (*s++ != delim) + { + xfree (respline); + return FTPINVPASV; + } } - { - unsigned short port2 = 0; - for (; ISDIGIT (*s); s++) - port2 = (*s - '0') + 10 * port2; - *port = (*port) * 256 + port2; - } + /* Finally, get the port number */ + tport = 0; + for (i = 1; ISDIGIT (*s); s++) + { + if (i > 5) + { + xfree (respline); + return FTPINVPASV; + } + tport = (*s - '0') + 10 * tport; + } + + /* Make sure that the response terminates correcty */ + if (*s++ != delim) + { + xfree (respline); + return FTPINVPASV; + } + + if (*s++ != ')') + { + xfree (respline); + return FTPINVPASV; + } + + *port = tport; + xfree (respline); return FTPOK; } +#endif /* Sends the TYPE request to the server. */ uerr_t @@ -755,7 +1095,7 @@ ftp_syst (struct rbuf *rbuf, enum stype *server_type) /* Skip the number (215, but 200 (!!!) in case of VMS) */ strtok (respline, " "); - + /* Which system type has been reported (we are interested just in the first word of the server response)? */ request = strtok (NULL, " "); @@ -812,7 +1152,7 @@ ftp_pwd (struct rbuf *rbuf, char **pwd) and everything following it. */ strtok (respline, "\""); request = strtok (NULL, "\""); - + /* Has the `pwd' been already allocated? Free! */ FREE_MAYBE (*pwd); diff --git a/src/ftp.c b/src/ftp.c index 7e997393..7c0bee7b 100644 --- a/src/ftp.c +++ b/src/ftp.c @@ -121,6 +121,110 @@ ftp_expected_bytes (const char *s) return res; } +#ifdef ENABLE_IPV6 +static int +getfamily (int fd) +{ + struct sockaddr_storage ss; + struct sockaddr *sa = (struct sockaddr *)&ss; + socklen_t len = sizeof (ss); + + assert (fd >= 0); + + if (getpeername (fd, sa, &len) < 0) + /* Mauro Tortonesi: HOW DO WE HANDLE THIS ERROR? */ + abort (); + + return sa->sa_family; +} + +/* + * This function sets up a passive data connection with the FTP server. + * It is merely a wrapper around ftp_epsv, ftp_lpsv and ftp_pasv. + */ +static uerr_t +ftp_do_pasv (struct rbuf *rbuf, ip_address *addr, unsigned short *port) +{ + uerr_t err; + int family; + + family = getfamily (rbuf->fd); + assert (family == AF_INET || family == AF_INET6); + + /* If our control connection is over IPv6, then we first try EPSV and then + * LPSV if the former is not supported. If the control connection is over + * IPv4, we simply issue the good old PASV request. */ + if (family == AF_INET6) + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> EPSV ... "); + err = ftp_epsv (rbuf, addr, port); + + /* If EPSV is not supported try LPSV */ + if (err == FTPNOPASV) + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> LPSV ... "); + err = ftp_lpsv (rbuf, addr, port); + } + } + else + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> PASV ... "); + err = ftp_pasv (rbuf, addr, port); + } + + return err; +} + +/* + * This function sets up an active data connection with the FTP server. + * It is merely a wrapper around ftp_eprt, ftp_lprt and ftp_port. + */ +static uerr_t +ftp_do_port (struct rbuf *rbuf) +{ + uerr_t err; + int family; + + assert (rbuf != NULL); + assert (rbuf_initialized_p (rbuf)); + + family = getfamily (rbuf->fd); + assert (family == AF_INET || family == AF_INET6); + + /* If our control connection is over IPv6, then we first try EPRT and then + * LPRT if the former is not supported. If the control connection is over + * IPv4, we simply issue the good old PORT request. */ + if (family == AF_INET6) + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> EPRT ... "); + err = ftp_eprt (rbuf); + + /* If EPRT is not supported try LPRT */ + if (err == FTPPORTERR) + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> LPRT ... "); + err = ftp_lprt (rbuf); + } + } + else + { + if (!opt.server_response) + logputs (LOG_VERBOSE, "==> PORT ... "); + err = ftp_port (rbuf); + } + + return err; +} +#else +#define ftp_do_pasv ftp_pasv +#define ftp_do_port ftp_port +#endif + /* Retrieves a file with denoted parameters through opening an FTP connection to the server. It always closes the data connection, and closes the control connection in case of error. */ @@ -542,7 +646,7 @@ Error in server response, closing control connection.\n")); unsigned short passive_port; if (!opt.server_response) logputs (LOG_VERBOSE, "==> PASV ... "); - err = ftp_pasv (&con->rbuf, &passive_addr, &passive_port); + err = ftp_do_pasv (&con->rbuf, &passive_addr, &passive_port); /* FTPRERR, WRITEFAILED, FTPNOPASV, FTPINVPASV */ switch (err) { @@ -579,13 +683,16 @@ Error in server response, closing control connection.\n")); } /* switch(err) */ if (err==FTPOK) { + DEBUGP (("trying to connect to %s port %d\n", + pretty_print_address (&passive_addr), + passive_port)); dtsock = connect_to_one (&passive_addr, passive_port, 1); if (dtsock < 0) { int save_errno = errno; CLOSE (csock); rbuf_uninitialize (&con->rbuf); - logprintf (LOG_VERBOSE, _("couldn't connect to %s:%hu: %s\n"), + logprintf (LOG_VERBOSE, _("couldn't connect to %s port %hu: %s\n"), pretty_print_address (&passive_addr), passive_port, strerror (save_errno)); return CONNECT_ERROR (save_errno); @@ -601,7 +708,7 @@ Error in server response, closing control connection.\n")); { if (!opt.server_response) logputs (LOG_VERBOSE, "==> PORT ... "); - err = ftp_port (&con->rbuf); + err = ftp_do_port (&con->rbuf); /* FTPRERR, WRITEFAILED, bindport (CONSOCKERR, CONPORTERR, BINDERR, LISTENERR), HOSTERR, FTPPORTERR */ switch (err) @@ -739,6 +846,7 @@ Error in server response, closing control connection.\n")); logprintf (LOG_VERBOSE, "==> RETR %s ... ", u->file); } } + err = ftp_retr (&con->rbuf, u->file); /* FTPRERR, WRITEFAILED, FTPNSFOD */ switch (err) @@ -930,6 +1038,7 @@ Error in server response, closing control connection.\n")); if (flush_res == EOF) res = -2; } + /* If get_contents couldn't write to fp, bail out. */ if (res == -2) { @@ -1224,7 +1333,7 @@ ftp_loop_internal (struct url *u, struct fileinfo *f, ccon *con) logprintf (LOG_NOTQUIET, "unlink: %s\n", strerror (errno)); } } - + /* Restore the original leave-pendingness. */ if (orig_lp) con->cmd |= LEAVE_PENDING; diff --git a/src/ftp.h b/src/ftp.h index a5e73927..33c4c419 100644 --- a/src/ftp.h +++ b/src/ftp.h @@ -51,8 +51,10 @@ uerr_t ftp_login PARAMS ((struct rbuf *, const char *, const char *)); uerr_t ftp_port PARAMS ((struct rbuf *)); uerr_t ftp_pasv PARAMS ((struct rbuf *, ip_address *, unsigned short *)); #ifdef ENABLE_IPV6 -uerr_t ftp_epsv PARAMS ((struct rbuf *, ip_address *, unsigned short *, - char *)); +uerr_t ftp_lprt PARAMS ((struct rbuf *)); +uerr_t ftp_lpsv PARAMS ((struct rbuf *, ip_address *, unsigned short *)); +uerr_t ftp_eprt PARAMS ((struct rbuf *)); +uerr_t ftp_epsv PARAMS ((struct rbuf *, ip_address *, unsigned short *)); #endif uerr_t ftp_type PARAMS ((struct rbuf *, int)); uerr_t ftp_cwd PARAMS ((struct rbuf *, const char *)); diff --git a/src/host.c b/src/host.c index 5297d0f3..d0fd7e7c 100644 --- a/src/host.c +++ b/src/host.c @@ -45,7 +45,7 @@ so, delete this exception statement from your version. */ #ifdef WINDOWS # include -# define SET_H_ERRNO(err) WSASetLastError(err) +# define SET_H_ERRNO(err) WSASetLastError (err) #else # include # include @@ -82,9 +82,9 @@ extern int h_errno; #endif #ifdef ENABLE_IPV6 -int ip_default_family = AF_INET6; +int ip_default_family = AF_UNSPEC; #else -int ip_default_family = AF_INET; +int ip_default_family = AF_INET; #endif /* Mapping between known hosts and to lists of their addresses. */ @@ -105,7 +105,7 @@ struct address_list { /* Get the bounds of the address list. */ void -address_list_get_bounds (struct address_list *al, int *start, int *end) +address_list_get_bounds (const struct address_list *al, int *start, int *end) { *start = al->faulty; *end = al->count; @@ -114,7 +114,7 @@ address_list_get_bounds (struct address_list *al, int *start, int *end) /* Copy address number INDEX to IP_STORE. */ void -address_list_copy_one (struct address_list *al, int index, ip_address *ip_store) +address_list_copy_one (const struct address_list *al, int index, ip_address *ip_store) { assert (index >= al->faulty && index < al->count); memcpy (ip_store, al->addresses + index, sizeof (ip_address)); @@ -123,14 +123,43 @@ address_list_copy_one (struct address_list *al, int index, ip_address *ip_store) /* Check whether two address lists have all their IPs in common. */ int -address_list_match_all (struct address_list *al1, struct address_list *al2) +address_list_match_all (const struct address_list *al1, const struct address_list *al2) { + int i; if (al1 == al2) return 1; if (al1->count != al2->count) return 0; - return 0 == memcmp (al1->addresses, al2->addresses, - al1->count * sizeof (ip_address)); + for (i = 0; i < al1->count; ++i) + { +#ifdef ENABLE_IPv6 + if (al1->addresses[i].type != al2->addresses[i].type) + return 0; + if (al1->addresses[i].type == IPv6_ADDRESS) + { + const struct in6_addr *addr1 = &al1->addresses[i].addr.ipv6.addr; + const struct in6_addr *addr2 = &al2->addresses[i].addr.ipv6.addr; + +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + if ((al1->addresses[i].address.scope_id + != al2->addresses[i].address.scope_id) + || !IN6_ARE_ADDR_EQUAL (addr1, addr2)) +#else + if (!IN6_ARE_ADDR_EQUAL (addr1, addr2)) +#endif + return 0; + } + else +#endif + { + const struct in_addr *addr1 = (const struct in_addr *)&al1->addresses[i].addr.ipv4.addr; + const struct in_addr *addr2 = (const struct in_addr *)&al2->addresses[i].addr.ipv4.addr; + + if (addr1->s_addr != addr2->s_addr) + return 0; + } + } + return 1; } /* Mark the INDEXth element of AL as faulty, so that the next time @@ -153,7 +182,7 @@ address_list_set_faulty (struct address_list *al, int index) al->faulty = 0; } -#ifdef HAVE_GETADDRINFO +#ifdef ENABLE_IPV6 /** * address_list_from_addrinfo * @@ -166,15 +195,15 @@ address_list_set_faulty (struct address_list *al, int index) * address_list* New allocated address_list */ static struct address_list * -address_list_from_addrinfo (struct addrinfo *ai) +address_list_from_addrinfo (const struct addrinfo *ai) { struct address_list *al; - struct addrinfo *ai_head = ai; + const struct addrinfo *ptr; int cnt = 0; int i; - for (ai = ai_head; ai; ai = ai->ai_next) - if (ai->ai_family == AF_INET || ai->ai_family == AF_INET6) + for (ptr = ai; ptr != NULL ; ptr = ptr->ai_next) + if (ptr->ai_family == AF_INET || ptr->ai_family == AF_INET6) ++cnt; if (cnt == 0) return NULL; @@ -185,17 +214,24 @@ address_list_from_addrinfo (struct addrinfo *ai) al->faulty = 0; al->refcount = 1; - for (i = 0, ai = ai_head; ai; ai = ai->ai_next) - if (ai->ai_family == AF_INET6) + for (i = 0, ptr = ai; ptr != NULL; ptr = ptr->ai_next) + if (ptr->ai_family == AF_INET6) { - struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)ai->ai_addr; - memcpy (al->addresses + i, &sin6->sin6_addr, 16); + const struct sockaddr_in6 *sin6 = + (const struct sockaddr_in6 *)ptr->ai_addr; + al->addresses[i].addr.ipv6.addr = sin6->sin6_addr; + al->addresses[i].type = IPv6_ADDRESS; +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + al->addresses[i].addr.ipv6.scope_id = sin6->sin6_scope_id; +#endif ++i; } - else if (ai->ai_family == AF_INET) + else if (ptr->ai_family == AF_INET) { - struct sockaddr_in *sin = (struct sockaddr_in *)ai->ai_addr; - map_ipv4_to_ip ((ip4_address *)&sin->sin_addr, al->addresses + i); + const struct sockaddr_in *sin = + (const struct sockaddr_in *)ptr->ai_addr; + al->addresses[i].addr.ipv4.addr = sin->sin_addr; + al->addresses[i].type = IPv4_ADDRESS; ++i; } assert (i == cnt); @@ -219,18 +255,20 @@ address_list_from_vector (char **h_addr_list) al->addresses = xmalloc (count * sizeof (ip_address)); al->refcount = 1; - for (i = 0; i < count; i++) - map_ipv4_to_ip ((ip4_address *)h_addr_list[i], al->addresses + i); + for (i = 0; i < count; i++) { + /* Mauro Tortonesi: is this safe? */ + memcpy (&((al->addresses + i)->addr.ipv4.addr.s_addr), h_addr_list[i], 4); + (al->addresses + i)->type = IPv4_ADDRESS; + } return al; } -#endif /* Like address_list_from_vector, but initialized with a single address. */ static struct address_list * -address_list_from_single (ip_address *addr) +address_list_from_single (const ip_address *addr) { struct address_list *al = xmalloc (sizeof (struct address_list)); al->count = 1; @@ -241,6 +279,7 @@ address_list_from_single (ip_address *addr) return al; } +#endif static void address_list_delete (struct address_list *al) @@ -262,58 +301,93 @@ address_list_release (struct address_list *al) } /** - * wget_sockaddr_set_address + * sockaddr_set_address * - * This function takes an wget_sockaddr and fill in the protocol type, - * the port number and the address, there NULL in address means wildcard. - * Unsuported adress family will abort the whole programm. + * This function takes a sockaddr struct and fills in the protocol type, + * the port number and the address. If ENABLE_IPV6 is defined, the sa + * parameter should point to a sockaddr_storage structure; if not, it + * should point to a sockaddr_in structure. + * If the address parameter is NULL, the function will use the unspecified + * address (0.0.0.0 for IPv4 and :: for IPv6). + * Unsupported address family will abort the whole programm. * * Input: - * wget_sockaddr* The space to be filled - * int The wished protocol + * struct sockaddr* The space to be filled * unsigned short The port - * const ip_address The Binary IP adress + * const ip_address The IP address * * Return: - * - Only modify 1. param + * - Only modifies 1st parameter. */ void -wget_sockaddr_set_address (wget_sockaddr *sa, - int ip_family, unsigned short port, ip_address *addr) +sockaddr_set_address (struct sockaddr *sa, unsigned short port, + const ip_address *addr) { - if (ip_family == AF_INET) + if (addr->type == IPv4_ADDRESS) { - sa->sin.sin_family = ip_family; - sa->sin.sin_port = htons (port); + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + + sin->sin_family = AF_INET; + sin->sin_port = htons (port); if (addr == NULL) - memset (&sa->sin.sin_addr, 0, sizeof(ip4_address)); + sin->sin_addr.s_addr = INADDR_ANY; else - { - ip4_address addr4; - if (!map_ip_to_ipv4 (addr, &addr4)) - /* should the callers have prevented this? */ - abort (); - memcpy (&sa->sin.sin_addr, &addr4, sizeof(ip4_address)); - } - return; + sin->sin_addr = addr->addr.ipv4.addr; } #ifdef ENABLE_IPV6 - if (ip_family == AF_INET6) + else if (addr->type == IPv6_ADDRESS) { - sa->sin6.sin6_family = ip_family; - sa->sin6.sin6_port = htons (port); + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + + sin6->sin6_family = AF_INET6; + sin6->sin6_port = htons (port); if (addr == NULL) - memset (&sa->sin6.sin6_addr, 0 , 16); - else - memcpy (&sa->sin6.sin6_addr, addr, 16); - return; + sin6->sin6_addr = in6addr_any; + else + sin6->sin6_addr = addr->addr.ipv6.addr; +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + sin6->sin6_scope_id = addr->addr.ipv6.scope_id; +#endif /* HAVE_SOCKADDR_IN6_SCOPE_ID */ + } +#endif /* ENABLE_IPV6 */ + else + abort (); +} + +void +sockaddr_get_address (const struct sockaddr *sa, unsigned short *port, + ip_address *addr) +{ + if (sa->sa_family == AF_INET) + { + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + + addr->type = IPv4_ADDRESS; + addr->addr.ipv4.addr = sin->sin_addr; + if (port != NULL) + *port = ntohs (sin->sin_port); + } +#ifdef ENABLE_IPV6 + else if (sa->sa_family == AF_INET6) + { + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + + addr->type = IPv6_ADDRESS; + addr->addr.ipv6.addr = sin6->sin6_addr; +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + addr->addr.ipv6.scope_id = sin6->sin6_scope_id; +#endif + if (port != NULL) + *port = ntohs (sin6->sin6_port); } #endif - abort(); + else + abort (); } +#if 0 /* currently unused */ /** - * wget_sockaddr_set_port + * sockaddr_set_port * * This funtion only fill the port of the socket information. * If the protocol is not supported nothing is done. @@ -330,54 +404,27 @@ wget_sockaddr_set_address (wget_sockaddr *sa, * - Only modify 1. param */ void -wget_sockaddr_set_port (wget_sockaddr *sa, unsigned short port) +sockaddr_set_port (struct sockaddr *sa, unsigned short port) { - if (sa->sa.sa_family == AF_INET) + if (sa->sa_family == AF_INET) { - sa->sin.sin_port = htons (port); - return; + struct sockaddr_in *sin = (struct sockaddr_in *)sa; + sin->sin_port = htons (port); } #ifdef ENABLE_IPV6 - if (sa->sa.sa_family == AF_INET6) + else if (sa->sa_family == AF_INET6) { - sa->sin6.sin6_port = htons (port); - return; + struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa; + sin6->sin6_port = htons (port); } #endif - abort(); + else + abort (); } - -/** - * wget_sockaddr_get_addr - * - * This function return the adress from an sockaddr as byte string. - * Unsuported adress family will abort the whole programm. - * - * Require: - * that the IP-Protocol already is set. - * - * Input: - * wget_sockaddr* Socket Information - * - * Output: - * unsigned char * IP address as byte string. - */ -void * -wget_sockaddr_get_addr (wget_sockaddr *sa) -{ - if (sa->sa.sa_family == AF_INET) - return &sa->sin.sin_addr; -#ifdef ENABLE_IPV6 - if (sa->sa.sa_family == AF_INET6) - return &sa->sin6.sin6_addr; #endif - abort(); - /* unreached */ - return NULL; -} /** - * wget_sockaddr_get_port + * sockaddr_get_port * * This function only return the port from the input structure * Unsuported adress family will abort the whole programm. @@ -392,15 +439,18 @@ wget_sockaddr_get_addr (wget_sockaddr *sa) * unsigned short Port Number in host order. */ unsigned short -wget_sockaddr_get_port (const wget_sockaddr *sa) +sockaddr_get_port (const struct sockaddr *sa) { - if (sa->sa.sa_family == AF_INET) - return htons (sa->sin.sin_port); + if (sa->sa_family == AF_INET) { + const struct sockaddr_in *sin = (const struct sockaddr_in *)sa; + return htons (sin->sin_port); #ifdef ENABLE_IPV6 - if (sa->sa.sa_family == AF_INET6) - return htons (sa->sin6.sin6_port); + } else if (sa->sa_family == AF_INET6) { + const struct sockaddr_in6 *sin6 = (const struct sockaddr_in6 *)sa; + return htons (sin6->sin6_port); #endif - abort(); + } else + abort (); /* do not complain about return nothing */ return -1; } @@ -419,58 +469,26 @@ wget_sockaddr_get_port (const wget_sockaddr *sa) * - Public IP-Family Information * * Output: - * socklen_t structure length for socket options + * int structure length for socket options */ socklen_t -sockaddr_len () +sockaddr_len (const struct sockaddr *sa) { - if (ip_default_family == AF_INET) - return sizeof (struct sockaddr_in); + if (sa->sa_family == AF_INET) + { + return sizeof (struct sockaddr_in); + } #ifdef ENABLE_IPV6 - if (ip_default_family == AF_INET6) - return sizeof (struct sockaddr_in6); + else if (sa->sa_family == AF_INET6) + { + return sizeof (struct sockaddr_in6); + } #endif - abort(); + else + abort (); /* do not complain about return nothing */ return 0; } - -/** - * Map an IPv4 adress to the internal adress format. - */ -void -map_ipv4_to_ip (ip4_address *ipv4, ip_address *ip) -{ -#ifdef ENABLE_IPV6 - static unsigned char ipv64[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; - memcpy ((char *)ip + 12, ipv4 , 4); - memcpy ((char *)ip + 0, ipv64, 12); -#else - if ((char *)ip != (char *)ipv4) - memcpy (ip, ipv4, 4); -#endif -} - -/* Detect whether an IP adress represents an IPv4 address and, if so, - copy it to IPV4. 0 is returned on failure. - This operation always succeeds when Wget is compiled without IPv6. - If IPV4 is NULL, don't copy, just detect. */ - -int -map_ip_to_ipv4 (ip_address *ip, ip4_address *ipv4) -{ -#ifdef ENABLE_IPV6 - static unsigned char ipv64[12] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff}; - if (0 != memcmp (ip, ipv64, 12)) - return 0; - if (ipv4) - memcpy (ipv4, (char *)ip + 12, 4); -#else - if (ipv4) - memcpy (ipv4, (char *)ip, 4); -#endif - return 1; -} /* Versions of gethostbyname and getaddrinfo that support timeout. */ @@ -555,21 +573,33 @@ getaddrinfo_with_timeout (const char *node, const char *service, inet_ntoa. With IPv6, it either prints an IPv6 address or an IPv4 address. */ -char * -pretty_print_address (ip_address *addr) +const char * +pretty_print_address (const ip_address *addr) { + switch (addr->type) + { + case IPv4_ADDRESS: + return inet_ntoa (addr->addr.ipv4.addr); #ifdef ENABLE_IPV6 - ip4_address addr4; - static char buf[128]; - - if (map_ip_to_ipv4 (addr, &addr4)) - return inet_ntoa (*(struct in_addr *)&addr4); - - if (!inet_ntop (AF_INET6, addr, buf, sizeof (buf))) - return ""; - return buf; + case IPv6_ADDRESS: + { + int len; + static char buf[128]; + inet_ntop (AF_INET6, &addr->addr.ipv6.addr, buf, sizeof (buf)); +#if 0 +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + /* print also scope_id for all ?non-global? addresses */ + snprintf (buf + len, sizeof (buf) - len, "%%%d", addr->addr.ipv6.scope_id); +#endif #endif - return inet_ntoa (*(struct in_addr *)addr); + len = strlen (buf); + buf[sizeof (buf) - 1] = '\0'; + return buf; + } +#endif + } + abort (); + return NULL; } /* Add host name HOST with the address ADDR_TEXT to the cache. @@ -598,28 +628,66 @@ cache_host_lookup (const char *host, struct address_list *al) } struct address_list * -lookup_host (const char *host, int silent) +lookup_host (const char *host, int flags) { struct address_list *al = NULL; - uint32_t addr_ipv4; - ip_address addr; - - /* First, try to check whether the address is already a numeric - address. */ #ifdef ENABLE_IPV6 - if (inet_pton (AF_INET6, host, &addr) > 0) - return address_list_from_single (&addr); + int err, family; + struct addrinfo hints, *res; + + /* This ip_default_family+flags business looks like bad design to + me. This function should rather accept a FAMILY argument. */ + if (flags & LH_IPv4_ONLY) + family = AF_INET; + else if (flags & LH_IPv6_ONLY) + family = AF_INET6; + else + family = ip_default_family; #endif + + /* First, try to check whether the address is already a numeric + address. Where getaddrinfo is available, we do it using the + AI_NUMERICHOST flag. Without IPv6, we check whether inet_addr + succeeds. */ - addr_ipv4 = (uint32_t)inet_addr (host); - if (addr_ipv4 != (uint32_t)-1) +#ifdef ENABLE_IPV6 + memset (&hints, 0, sizeof (hints)); + hints.ai_family = family; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_NUMERICHOST; + if (flags & LH_PASSIVE) + hints.ai_flags = AI_PASSIVE; + + /* no need to call getaddrinfo_with_timeout here, as we're not + * relying on the DNS, but we're only doing an address translation + * from presentation (ASCII) to network format */ + err = getaddrinfo (host, NULL, &hints, &res); + if (err == 0 && res != NULL) { - /* ADDR is defined to be in network byte order, which is what - this returns, so we can just copy it to STORE_IP. */ - map_ipv4_to_ip ((ip4_address *)&addr_ipv4, &addr); - return address_list_from_single (&addr); + al = address_list_from_addrinfo (res); + freeaddrinfo (res); + return al; } +#else + { + uint32_t addr_ipv4 = (uint32_t)inet_addr (host); + if (addr_ipv4 != (uint32_t) -1) + { + /* The return value of inet_addr is in network byte order, so + we can just copy it to ADDR. */ + ip_address addr; + /* This has a huge number of dereferences because C doesn't + support anonymous unions and because struct in_addr adds a + cast. */ + addr.addr.ipv4.addr.s_addr = addr_ipv4; + addr.type = IPv4_ADDRESS; + return address_list_from_single (&addr); + } + } +#endif + + /* Then, try to find the host in the cache. */ if (host_name_addresses_map) { @@ -632,41 +700,37 @@ lookup_host (const char *host, int silent) } } - if (!silent) + if (!(flags & LH_SILENT)) logprintf (LOG_VERBOSE, _("Resolving %s... "), host); /* Host name lookup goes on below. */ -#ifdef HAVE_GETADDRINFO +#ifdef ENABLE_IPV6 { - struct addrinfo hints, *ai; - int err; - memset (&hints, 0, sizeof (hints)); - if (ip_default_family == AF_INET) - hints.ai_family = AF_INET; - else - hints.ai_family = PF_UNSPEC; + hints.ai_family = family; hints.ai_socktype = SOCK_STREAM; - err = getaddrinfo_with_timeout (host, NULL, &hints, &ai, opt.dns_timeout); + if (flags & LH_PASSIVE) + hints.ai_flags = AI_PASSIVE; + + err = getaddrinfo_with_timeout (host, NULL, &hints, &res, opt.dns_timeout); - if (err != 0 || ai == NULL) + if (err != 0 || res == NULL) { - if (!silent) + if (!(flags & LH_SILENT)) logprintf (LOG_VERBOSE, _("failed: %s.\n"), err != EAI_SYSTEM ? gai_strerror (err) : strerror (errno)); return NULL; } - al = address_list_from_addrinfo (ai); - freeaddrinfo (ai); + al = address_list_from_addrinfo (res); + freeaddrinfo (res); } #else { - struct hostent *hptr; - hptr = gethostbyname_with_timeout (host, opt.dns_timeout); + struct hostent *hptr = gethostbyname_with_timeout (host, opt.dns_timeout); if (!hptr) { - if (!silent) + if (!(flags & LH_SILENT)) { if (errno != ETIMEDOUT) logprintf (LOG_VERBOSE, _("failed: %s.\n"), herrmsg (h_errno)); @@ -675,6 +739,7 @@ lookup_host (const char *host, int silent) } return NULL; } + assert (hptr->h_length == 4); /* Do all systems have h_addr_list, or is it a newer thing? If the latter, use address_list_from_single. */ al = address_list_from_vector (hptr->h_addr_list); @@ -683,7 +748,7 @@ lookup_host (const char *host, int silent) /* Print the addresses determined by DNS lookup, but no more than three. */ - if (!silent) + if (!(flags & LH_SILENT)) { int i; int printmax = al->count <= 3 ? al->count : 3; diff --git a/src/host.h b/src/host.h index 3b863aab..46acacb1 100644 --- a/src/host.h +++ b/src/host.h @@ -6,7 +6,7 @@ This file is part of GNU Wget. GNU Wget is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or -(at your option) any later version. + (at your option) any later version. GNU Wget is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of @@ -44,64 +44,71 @@ so, delete this exception statement from your version. */ struct url; struct address_list; -/* wget_sockaddr is used instead of sockaddr where an IPV6 address - must fit. */ - -typedef union { - struct sockaddr sa; /* Generic but too small */ - struct sockaddr_in sin; /* IPv4 socket address */ -#ifdef ENABLE_IPV6 - struct sockaddr_in6 sin6; /* IPv6 socket address */ -#endif -} wget_sockaddr; +extern int ip_default_family; /* defined in host.c */ typedef struct { - unsigned char bytes[4]; -} ip4_address; + enum { + IPv4_ADDRESS, +#ifdef ENABLE_IPV6 + IPv6_ADDRESS +#endif /* ENABLE_IPV6 */ + } type; + union { +#ifdef ENABLE_IPV6 + struct { + struct in6_addr addr; +#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID + unsigned int scope_id; +#endif /* HAVE_SOCKADDR_IN6_SCOPE_ID */ + } ipv6; +#endif /* ENABLE_IPV6 */ + struct { + struct in_addr addr; + } ipv4; + } addr; +} ip_address; -/* If compiled with IPv6 support, we internally represent all IP - addresses as IPv6 addresses. IPv4 addresses are dynamically mapped - to IPv6, i.e. stored in the format ::ffff:. */ +#ifndef ENABLE_IPV6 -#ifdef ENABLE_IPV6 -# define MAX_IP_ADDRESS_SIZE 16 -#else -# define MAX_IP_ADDRESS_SIZE 4 +#ifndef HAVE_SOCKADDR_STORAGE +#define sockaddr_storage sockaddr_in #endif -typedef struct { - unsigned char bytes[MAX_IP_ADDRESS_SIZE]; -} ip_address; +#endif /* ENABLE_IPV6 */ + +/* Flags for lookup_host */ +#define LH_SILENT 0x0001 +#define LH_PASSIVE 0x0002 +#define LH_IPv4_ONLY 0x0004 +#ifdef ENABLE_IPV6 +#define LH_IPv6_ONLY 0x0008 +#endif /* ENABLE_IPV6 */ /* Function declarations */ struct address_list *lookup_host PARAMS ((const char *, int)); char *herrmsg PARAMS ((int)); -void address_list_get_bounds PARAMS ((struct address_list *, int *, int *)); -void address_list_copy_one PARAMS ((struct address_list *, int, +void address_list_get_bounds PARAMS ((const struct address_list *, + int *, int *)); +void address_list_copy_one PARAMS ((const struct address_list *, int, ip_address *)); -int address_list_match_all PARAMS ((struct address_list *, - struct address_list *)); +int address_list_match_all PARAMS ((const struct address_list *, + const struct address_list *)); void address_list_set_faulty PARAMS ((struct address_list *, int)); void address_list_release PARAMS ((struct address_list *)); -char *pretty_print_address PARAMS ((ip_address *)); +const char *pretty_print_address PARAMS ((const ip_address *)); int accept_domain PARAMS ((struct url *)); int sufmatch PARAMS ((const char **, const char *)); -void host_cleanup PARAMS ((void)); - -void wget_sockaddr_set_address PARAMS((wget_sockaddr *, int, - unsigned short, ip_address *)); -void wget_sockaddr_set_port PARAMS((wget_sockaddr *, unsigned short)); -void *wget_sockaddr_get_addr PARAMS((wget_sockaddr *)); -unsigned short wget_sockaddr_get_port PARAMS((const wget_sockaddr *)); -socklen_t sockaddr_len PARAMS(()); -void map_ipv4_to_ip PARAMS((ip4_address *, ip_address *)); -int map_ip_to_ipv4 PARAMS((ip_address *, ip4_address *)); - -extern int ip_default_family; /* defined in host.c */ +void sockaddr_set_address PARAMS ((struct sockaddr *, unsigned short, + const ip_address *)); +void sockaddr_get_address PARAMS ((const struct sockaddr *, unsigned short *, + ip_address *)); +unsigned short sockaddr_get_port PARAMS ((const struct sockaddr *)); +socklen_t sockaddr_len PARAMS ((const struct sockaddr *sa)); +void host_cleanup PARAMS ((void)); #endif /* HOST_H */ diff --git a/src/http.c b/src/http.c index f683b628..d4ee74c0 100644 --- a/src/http.c +++ b/src/http.c @@ -441,7 +441,7 @@ register_persistent (const char *host, unsigned short port, int fd) /* This lookup_host cannot fail, because it has the results in the cache. */ - pc_last_host_ip = lookup_host (host, 1); + pc_last_host_ip = lookup_host (host, LH_SILENT); assert (pc_last_host_ip != NULL); pc_last_port = port; @@ -496,7 +496,7 @@ persistent_available_p (const char *host, unsigned short port) return 0; #endif /* HAVE_SSL */ - this_host_ip = lookup_host (host, 1); + this_host_ip = lookup_host (host, LH_SILENT); if (!this_host_ip) return 0; -- 2.39.2