]> sjero.net Git - wget/blobdiff - src/connect.c
[svn] Correctly check for unsupported socket families.
[wget] / src / connect.c
index fcd8aec2534c250c90648e518d4787d7dd2ccca2..f73c9501cc9d5514584649e5f6f1e5e17a514a8d 100644 (file)
@@ -106,7 +106,7 @@ sockaddr_set_data (struct sockaddr *sa, const ip_address *ip, int port)
    you're not interested in one or the other information, pass NULL as
    the pointer.  */
 
-void
+static void
 sockaddr_get_data (const struct sockaddr *sa, ip_address *ip, int *port)
 {
   switch (sa->sa_family)
@@ -166,28 +166,41 @@ sockaddr_size (const struct sockaddr *sa)
 }
 \f
 static int
-resolve_bind_address (const char *host, struct sockaddr *sa, int flags)
+resolve_bind_address (struct sockaddr *sa)
 {
   struct address_list *al;
 
-  /* #### Shouldn't we do this only once?  opt.bind_address won't
-     change during a Wget run!  */
+  /* Make sure this is called only once.  opt.bind_address doesn't
+     change during a Wget run.  */
+  static int called, should_bind;
+  static ip_address ip;
+  if (called)
+    {
+      if (should_bind)
+       sockaddr_set_data (sa, &ip, 0);
+      return should_bind;
+    }
+  called = 1;
 
-  al = lookup_host (host, flags | LH_SILENT | LH_PASSIVE);
-  if (al == NULL)
+  al = lookup_host_passive (opt.bind_address);
+  if (!al)
     {
-      /* #### We should print the error message here. */
+      /* #### We should be able to print the error message here. */
       logprintf (LOG_NOTQUIET,
                 _("%s: unable to resolve bind address `%s'; disabling bind.\n"),
                 exec_name, opt.bind_address);
+      should_bind = 0;
       return 0;
     }
 
   /* Pick the first address in the list and use it as bind address.
-     Perhaps we should try multiple addresses, but I don't think
-     that's necessary in practice.  */
-  sockaddr_set_data (sa, address_list_address_at (al, 0), 0);
+     Perhaps we should try multiple addresses in succession, but I
+     don't think that's necessary in practice.  */
+  ip = *address_list_address_at (al, 0);
   address_list_release (al);
+
+  sockaddr_set_data (sa, &ip, 0);
+  should_bind = 1;
   return 1;
 }
 \f
@@ -280,7 +293,7 @@ connect_to_ip (const ip_address *ip, int port, const char *print)
         address.  */
       struct sockaddr_storage bind_ss;
       struct sockaddr *bind_sa = (struct sockaddr *)&bind_ss;
-      if (resolve_bind_address (opt.bind_address, bind_sa, 0))
+      if (resolve_bind_address (bind_sa))
        {
           if (bind (sock, bind_sa, sockaddr_size (bind_sa)) < 0)
            goto err;
@@ -343,12 +356,16 @@ connect_to_host (const char *host, int port)
     }
   address_list_release (al);
 
-  if (sock < 0 && address_list_cached_p (al))
+  if (sock >= 0)
+    /* Mark a successful connection to one of the addresses. */
+    address_list_set_connected (al);
+
+  if (sock < 0 && address_list_connected_p (al))
     {
-      /* We were unable to connect to any address in a list we've
-        obtained from cache.  There is a possibility that the host is
-        under dynamic DNS and has changed its address.  Resolve it
-        again.  */
+      /* We are unable to connect to any of HOST's addresses, although
+        we were previously able to connect to HOST.  That might
+        indicate that HOST is under dynamic DNS and the addresses
+        we're connecting to have expired.  Resolve it again.  */
       forget_host_lookup (host);
       goto again;
     }
@@ -387,12 +404,21 @@ test_socket_open (int sock)
 #endif
 }
 
-/* Create a socket and bind it to PORT locally.  Calling accept() on
-   such a socket waits for and accepts incoming TCP connections.  The
-   resulting socket is stored to LOCAL_SOCK.  */
+/* Create a socket, bind it to local interface BIND_ADDRESS on port
+   *PORT, set up a listen backlog, and return the resulting socket, or
+   -1 in case of error.
+
+   BIND_ADDRESS is the address of the interface to bind to.  If it is
+   NULL, the socket is bound to the default address.  PORT should
+   point to the port number that will be used for the binding.  If
+   that number is 0, the system will choose a suitable port, and the
+   chosen value will be written to *PORT.
 
-uerr_t
-bindport (const ip_address *bind_address, int *port, int *local_sock)
+   Calling accept() on such a socket waits for and accepts incoming
+   TCP connections.  */
+
+int
+bind_local (const ip_address *bind_address, int *port)
 {
   int sock;
   int family = AF_INET;
@@ -409,8 +435,9 @@ bindport (const ip_address *bind_address, int *port, int *local_sock)
     family = AF_INET6;
 #endif
 
-  if ((sock = socket (family, SOCK_STREAM, 0)) < 0)
-    return CONSOCKERR;
+  sock = socket (family, SOCK_STREAM, 0);
+  if (sock < 0)
+    return -1;
 
 #ifdef SO_REUSEADDR
   setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, setopt_ptr, setopt_size);
@@ -428,16 +455,22 @@ bindport (const ip_address *bind_address, int *port, int *local_sock)
   if (bind (sock, sa, sockaddr_size (sa)) < 0)
     {
       xclose (sock);
-      return BINDERR;
+      return -1;
     }
   DEBUGP (("Local socket fd %d bound.\n", sock));
-  if (!*port)
+
+  /* If *PORT is 0, find out which port we've bound to.  */
+  if (*port == 0)
     {
       socklen_t sa_len = sockaddr_size (sa);
       if (getsockname (sock, sa, &sa_len) < 0)
        {
+         /* If we can't find out the socket's local address ("name"),
+            something is seriously wrong with the socket, and it's
+            unusable for us anyway because we must know the chosen
+            port.  */
          xclose (sock);
-         return CONPORTERR;
+         return -1;
        }
       sockaddr_get_data (sa, NULL, port);
       DEBUGP (("binding to address %s using port %i.\n", 
@@ -446,80 +479,69 @@ bindport (const ip_address *bind_address, int *port, int *local_sock)
   if (listen (sock, 1) < 0)
     {
       xclose (sock);
-      return LISTENERR;
+      return -1;
     }
-  *local_sock = sock;
-  return BINDOK;
+  return sock;
 }
 
-#ifdef HAVE_SELECT
-/* Wait for file descriptor FD to be readable or writable or both,
-   timing out after MAXTIME seconds.  Returns 1 if FD is available, 0
-   for timeout and -1 for error.  The argument WAIT_FOR can be a
-   combination of WAIT_READ and WAIT_WRITE.
-
-   This is a mere convenience wrapper around the select call, and
-   should be taken as such.  */
-
-int
-select_fd (int fd, double maxtime, int wait_for)
-{
-  fd_set fdset;
-  fd_set *rd = NULL, *wr = NULL;
-  struct timeval tmout;
-  int result;
-
-  FD_ZERO (&fdset);
-  FD_SET (fd, &fdset);
-  if (wait_for & WAIT_FOR_READ)
-    rd = &fdset;
-  if (wait_for & WAIT_FOR_WRITE)
-    wr = &fdset;
-
-  tmout.tv_sec = (long) maxtime;
-  tmout.tv_usec = 1000000L * (maxtime - (long) maxtime);
-
-  do
-    result = select (fd + 1, rd, wr, NULL, &tmout);
-  while (result < 0 && errno == EINTR);
+/* Like a call to accept(), but with the added check for timeout.
 
-  return result;
-}
-#endif /* HAVE_SELECT */
+   In other words, accept a client connection on LOCAL_SOCK, and
+   return the new socket used for communication with the client.
+   LOCAL_SOCK should have been bound, e.g. using bind_local().
 
-/* Accept a connection on LOCAL_SOCK, and store the new socket to
-   *SOCK.  It blocks the caller until a connection is established.  If
-   no connection is established for opt.connect_timeout seconds, the
+   The caller is blocked until a connection is established.  If no
+   connection is established for opt.connect_timeout seconds, the
    function exits with an error status.  */
 
-uerr_t
-acceptport (int local_sock, int *sock)
+int
+accept_connection (int local_sock)
 {
+  int sock;
+
+  /* We don't need the values provided by accept, but accept
+     apparently requires them to be present.  */
   struct sockaddr_storage ss;
   struct sockaddr *sa = (struct sockaddr *)&ss;
   socklen_t addrlen = sizeof (ss);
 
 #ifdef HAVE_SELECT
   if (opt.connect_timeout)
-    if (select_fd (local_sock, opt.connect_timeout, WAIT_FOR_READ) <= 0)
-      return ACCEPTERR;
+    {
+      int test = select_fd (local_sock, opt.connect_timeout, WAIT_FOR_READ);
+      if (test == 0)
+       errno = ETIMEDOUT;
+      if (test <= 0)
+       return -1;
+    }
 #endif
-  if ((*sock = accept (local_sock, sa, &addrlen)) < 0)
-    return ACCEPTERR;
-  DEBUGP (("Created socket fd %d.\n", *sock));
-  return ACCEPTOK;
+  sock = accept (local_sock, sa, &addrlen);
+  DEBUGP (("Accepted client at socket %d.\n", sock));
+  return sock;
 }
 
-/* Return the local IP address associated with the connection on FD.  */
+/* Get the IP address associated with the connection on FD and store
+   it to IP.  Return 1 on success, 0 otherwise.
+
+   If ENDPOINT is ENDPOINT_LOCAL, it returns the address of the local
+   (client) side of the socket.  Else if ENDPOINT is ENDPOINT_PEER, it
+   returns the address of the remote (peer's) side of the socket.  */
 
 int
-conaddr (int fd, ip_address *ip)
+socket_ip_address (int sock, ip_address *ip, int endpoint)
 {
   struct sockaddr_storage storage;
   struct sockaddr *sockaddr = (struct sockaddr *)&storage;
   socklen_t addrlen = sizeof (storage);
+  int ret;
 
-  if (getsockname (fd, sockaddr, &addrlen) < 0)
+  if (endpoint == ENDPOINT_LOCAL)
+    ret = getsockname (sock, sockaddr, &addrlen);
+  else if (endpoint == ENDPOINT_PEER)
+    ret = getpeername (sock, sockaddr, &addrlen);
+  else
+    abort ();
+  if (ret < 0)
     return 0;
 
   switch (sockaddr->sa_family)
@@ -551,6 +573,73 @@ conaddr (int fd, ip_address *ip)
 
   return 0;
 }
+
+/* Return non-zero of the errno code passed to the function is a
+   result of an attempt to create a socket of unsupported family.  */
+
+int
+unsupported_socket_family_error (int err)
+{
+  /* Have to guard against some of these values not being defined.
+     Cannot use switch because some of the values might be equal.  */
+#ifdef EAFNOSUPPORT
+  if (err == EAFNOSUPPORT) return 1;
+#endif
+#ifdef EPFNOSUPPORT
+  if (err == EPFNOSUPPORT) return 1;
+#endif
+#ifdef ESOCKTNOSUPPORT         /* no, "sockt" is not a typo! */
+  if (err == ESOCKTNOSUPPORT) return 1;
+#endif
+#ifdef EPROTONOSUPPORT
+  if (err == EPROTONOSUPPORT) return 1;
+#endif
+#ifdef ENOPROTOOPT
+  if (err == ENOPROTOOPT) return 1;
+#endif
+  /* Apparently, older versions of Linux used EINVAL instead of
+     EAFNOSUPPORT. */
+  if (err == EINVAL) return 1;
+
+  return 0;
+}
+
+#ifdef HAVE_SELECT
+
+/* Wait for file descriptor FD to be readable or writable or both,
+   timing out after MAXTIME seconds.  Returns 1 if FD is available, 0
+   for timeout and -1 for error.  The argument WAIT_FOR can be a
+   combination of WAIT_READ and WAIT_WRITE.
+
+   This is a mere convenience wrapper around the select call, and
+   should be taken as such.  */
+
+int
+select_fd (int fd, double maxtime, int wait_for)
+{
+  fd_set fdset;
+  fd_set *rd = NULL, *wr = NULL;
+  struct timeval tmout;
+  int result;
+
+  FD_ZERO (&fdset);
+  FD_SET (fd, &fdset);
+  if (wait_for & WAIT_FOR_READ)
+    rd = &fdset;
+  if (wait_for & WAIT_FOR_WRITE)
+    wr = &fdset;
+
+  tmout.tv_sec = (long) maxtime;
+  tmout.tv_usec = 1000000L * (maxtime - (long) maxtime);
+
+  do
+    result = select (fd + 1, rd, wr, NULL, &tmout);
+  while (result < 0 && errno == EINTR);
+
+  return result;
+}
+
+#endif /* HAVE_SELECT */
 \f
 /* Basic socket operations, mostly EINTR wrappers.  */
 
@@ -624,11 +713,22 @@ struct extended_info {
   void *ctx;
 };
 
+/* Register the handlers for operations on FD.  This is meant
+   primarily for transport layers like SSL that piggyback on sockets,
+   but with their own readers, writers, etc.  */
+
 void
 register_extended (int fd, xreader_t reader, xwriter_t writer,
                   xpoller_t poller, xcloser_t closer, void *ctx)
 {
-  struct extended_info *info = xnew (struct extended_info);
+  struct extended_info *info;
+
+  /* The file descriptor must be non-negative to be registered.
+     Negative values are ignored by xclose(), and -1 cannot be used as
+     hash key.  */
+  assert (fd >= 0);
+
+  info = xnew (struct extended_info);
   info->reader = reader;
   info->writer = writer;
   info->poller = poller;
@@ -660,6 +760,7 @@ register_extended (int fd, xreader_t reader, xwriter_t writer,
     {                                                                  \
       info = hash_table_get (extended_map, (void *) fd);               \
       last_fd = fd;                                                    \
+      last_info = info;                                                        \
       last_tick = extended_map_modified_tick;                          \
     }                                                                  \
 } while (0)
@@ -753,12 +854,14 @@ xclose (int fd)
     info = hash_table_get (extended_map, (void *) fd);
 
   if (info && info->closer)
+    info->closer (fd, info->ctx);
+  else
+    sock_close (fd);
+
+  if (info)
     {
-      info->closer (fd, info->ctx);
       hash_table_remove (extended_map, (void *) fd);
       xfree (info);
       ++extended_map_modified_tick;
     }
-  else
-    sock_close (fd);
 }