]> sjero.net Git - wget/blobdiff - src/connect.c
[svn] Remove the "rbuf" buffering layer. Provide peeking primitives instead.
[wget] / src / connect.c
index 8db283998ead7ec8eaf7a2208ff8266558dabe96..eeb4eb288f9a9d7b5782d8a93b14c28a0486d445 100644 (file)
@@ -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
@@ -30,22 +30,21 @@ so, delete this exception statement from your version.  */
 #include <config.h>
 
 #include <stdio.h>
+#include <stdlib.h>
 #include <sys/types.h>
 #ifdef HAVE_UNISTD_H
 # include <unistd.h>
 #endif
 #include <assert.h>
 
-#ifdef WINDOWS
-# include <winsock.h>
-#else
+#ifndef WINDOWS
 # include <sys/socket.h>
 # include <netdb.h>
 # include <netinet/in.h>
-#ifndef __BEOS__
-# include <arpa/inet.h>
-#endif
-#endif /* WINDOWS */
+# ifndef __BEOS__
+#  include <arpa/inet.h>
+# endif
+#endif /* not WINDOWS */
 
 #include <errno.h>
 #ifdef HAVE_STRING_H
@@ -61,45 +60,154 @@ so, delete this exception statement from your version.  */
 #include "utils.h"
 #include "host.h"
 #include "connect.h"
+#include "hash.h"
 
 #ifndef errno
 extern int errno;
 #endif
 
-/* Variables shared by bindport and acceptport: */
-static int msock = -1;
-static struct sockaddr *addr;
+\f
+/* Fill SA as per the data in IP and PORT.  SA shoult point to struct
+   sockaddr_storage if ENABLE_IPV6 is defined, to struct sockaddr_in
+   otherwise.  */
+
+static void
+sockaddr_set_data (struct sockaddr *sa, const ip_address *ip, int port)
+{
+  switch (ip->type)
+    {
+    case IPV4_ADDRESS:
+      {
+       struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+       sin->sin_family = AF_INET;
+       sin->sin_port = htons (port);
+       sin->sin_addr = ADDRESS_IPV4_IN_ADDR (ip);
+       break;
+      }
+#ifdef ENABLE_IPV6
+    case IPV6_ADDRESS:
+      {
+       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+       sin6->sin6_family = AF_INET6;
+       sin6->sin6_port = htons (port);
+       sin6->sin6_addr = ADDRESS_IPV6_IN6_ADDR (ip);
+#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
+       sin6->sin6_scope_id = ADDRESS_IPV6_SCOPE (ip);
+#endif
+       break;
+      }
+#endif /* ENABLE_IPV6 */
+    default:
+      abort ();
+    }
+}
 
-static ip_address bind_address;
-static int bind_address_resolved;
+/* Get the data of SA, specifically the IP address and the port.  If
+   you're not interested in one or the other information, pass NULL as
+   the pointer.  */
 
 static void
-resolve_bind_address (void)
+sockaddr_get_data (const struct sockaddr *sa, ip_address *ip, int *port)
+{
+  switch (sa->sa_family)
+    {
+    case AF_INET:
+      {
+       struct sockaddr_in *sin = (struct sockaddr_in *)sa;
+       if (ip)
+         {
+           ip->type = IPV4_ADDRESS;
+           ADDRESS_IPV4_IN_ADDR (ip) = sin->sin_addr;
+         }
+       if (port)
+         *port = ntohs (sin->sin_port);
+       break;
+      }
+#ifdef ENABLE_IPV6
+    case AF_INET6:
+      {
+       struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)sa;
+       if (ip)
+         {
+           ip->type = IPV6_ADDRESS;
+           ADDRESS_IPV6_IN6_ADDR (ip) = sin6->sin6_addr;
+#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
+           ADDRESS_IPV6_SCOPE (ip) = sin6->sin6_scope_id;
+#endif
+         }
+       if (port)
+         *port = ntohs (sin6->sin6_port);
+       break;
+      }
+#endif
+    default:
+      abort ();
+    }
+}
+
+/* Return the size of the sockaddr structure depending on its
+   family.  */
+
+static socklen_t
+sockaddr_size (const struct sockaddr *sa)
+{
+  switch (sa->sa_family)
+    {
+    case AF_INET:
+      return sizeof (struct sockaddr_in);
+#ifdef ENABLE_IPV6
+    case AF_INET6:
+      return sizeof (struct sockaddr_in6);
+#endif
+    default:
+      abort ();
+      return 0;                        /* so the compiler shuts up. */
+    }
+}
+\f
+static int
+resolve_bind_address (struct sockaddr *sa)
 {
   struct address_list *al;
 
-  if (bind_address_resolved || opt.bind_address == NULL)
-    /* Nothing to do. */
-    return;
+  /* 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 (opt.bind_address, 1);
+  al = lookup_host (opt.bind_address, LH_BIND | LH_SILENT);
   if (!al)
     {
+      /* #### We should be able to print the error message here. */
       logprintf (LOG_NOTQUIET,
-                _("Unable to convert `%s' to a bind address.  Reverting to ANY.\n"),
-                opt.bind_address);
-      return;
+                _("%s: unable to resolve bind address `%s'; disabling bind.\n"),
+                exec_name, opt.bind_address);
+      should_bind = 0;
+      return 0;
     }
 
-  address_list_copy_one (al, 0, &bind_address);
+  /* Pick the first address in the list and use it as bind address.
+     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);
-  bind_address_resolved = 1;
+
+  sockaddr_set_data (sa, &ip, 0);
+  should_bind = 1;
+  return 1;
 }
 \f
 struct cwt_context {
   int fd;
   const struct sockaddr *addr;
-  int addrlen;
+  socklen_t addrlen;
   int result;
 };
 
@@ -115,8 +223,8 @@ connect_with_timeout_callback (void *arg)
    ETIMEDOUT.  */
 
 static int
-connect_with_timeout (int fd, const struct sockaddr *addr, int addrlen,
-                     int timeout)
+connect_with_timeout (int fd, const struct sockaddr *addr, socklen_t addrlen,
+                     double timeout)
 {
   struct cwt_context ctx;
   ctx.fd = fd;
@@ -133,114 +241,428 @@ connect_with_timeout (int fd, const struct sockaddr *addr, int addrlen,
   return ctx.result;
 }
 \f
-/* A kludge, but still better than passing the host name all the way
-   to connect_to_one.  */
-static const char *connection_host_name;
+/* Connect via TCP to the specified address and port.
 
-void
-set_connection_host_name (const char *host)
+   If PRINT is non-NULL, it is the host name to print that we're
+   connecting to.  */
+
+int
+connect_to_ip (const ip_address *ip, int port, const char *print)
 {
-  if (host)
-    assert (connection_host_name == NULL);
-  else
-    assert (connection_host_name != NULL);
+  struct sockaddr_storage ss;
+  struct sockaddr *sa = (struct sockaddr *)&ss;
+  int sock = -1;
+
+  /* If PRINT is non-NULL, print the "Connecting to..." line, with
+     PRINT being the host name we're connecting to.  */
+  if (print)
+    {
+      const char *txt_addr = pretty_print_address (ip);
+      if (print && 0 != strcmp (print, txt_addr))
+       logprintf (LOG_VERBOSE,
+                  _("Connecting to %s|%s|:%d... "), print, txt_addr, port);
+      else
+       logprintf (LOG_VERBOSE, _("Connecting to %s:%d... "), txt_addr, port);
+    }
+
+  /* Store the sockaddr info to SA.  */
+  sockaddr_set_data (sa, ip, port);
 
-  connection_host_name = host;
+  /* Create the socket of the family appropriate for the address.  */
+  sock = socket (sa->sa_family, SOCK_STREAM, 0);
+  if (sock < 0)
+    goto err;
+
+  /* For very small rate limits, set the buffer size (and hence,
+     hopefully, the kernel's TCP window size) to the per-second limit.
+     That way we should never have to sleep for more than 1s between
+     network reads.  */
+  if (opt.limit_rate && opt.limit_rate < 8192)
+    {
+      int bufsize = opt.limit_rate;
+      if (bufsize < 512)
+       bufsize = 512;          /* avoid pathologically small values */
+#ifdef SO_RCVBUF
+      setsockopt (sock, SOL_SOCKET, SO_RCVBUF,
+                 (void *)&bufsize, (socklen_t)sizeof (bufsize));
+#endif
+      /* When we add limit_rate support for writing, which is useful
+        for POST, we should also set SO_SNDBUF here.  */
+    }
+
+  if (opt.bind_address)
+    {
+      /* Bind the client side of the socket to the requested
+        address.  */
+      struct sockaddr_storage bind_ss;
+      struct sockaddr *bind_sa = (struct sockaddr *)&bind_ss;
+      if (resolve_bind_address (bind_sa))
+       {
+          if (bind (sock, bind_sa, sockaddr_size (bind_sa)) < 0)
+           goto err;
+       }
+    }
+
+  /* Connect the socket to the remote endpoint.  */
+  if (connect_with_timeout (sock, sa, sockaddr_size (sa),
+                           opt.connect_timeout) < 0)
+    goto err;
+
+  /* Success. */
+  assert (sock >= 0);
+  if (print)
+    logprintf (LOG_VERBOSE, _("connected.\n"));
+  DEBUGP (("Created socket %d.\n", sock));
+  return sock;
+
+ err:
+  {
+    /* Protect errno from possible modifications by close and
+       logprintf.  */
+    int save_errno = errno;
+    if (sock >= 0)
+      fd_close (sock);
+    if (print)
+      logprintf (LOG_VERBOSE, "failed: %s.\n", strerror (errno));
+    errno = save_errno;
+    return -1;
+  }
 }
 
-/* Connect to a remote host whose address has been resolved. */
+/* Connect via TCP to a remote host on the specified port.
+
+   HOST is resolved as an Internet host name.  If HOST resolves to
+   more than one IP address, they are tried in the order returned by
+   DNS until connecting to one of them succeeds.  */
+
 int
-connect_to_one (ip_address *addr, unsigned short port, int silent)
+connect_to_host (const char *host, int port)
 {
-  wget_sockaddr sa;
-  int sock, save_errno;
+  int i, start, end;
+  int sock;
 
-  /* Set port and protocol */
-  wget_sockaddr_set_address (&sa, ip_default_family, port, addr);
+  struct address_list *al = lookup_host (host, 0);
 
-  if (!silent)
+ retry:
+  if (!al)
+    return E_HOST;
+
+  address_list_get_bounds (al, &start, &end);
+  for (i = start; i < end; i++)
     {
-      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... "),
-                  connection_host_name, pretty_addr, port);
-      else
-       logprintf (LOG_VERBOSE, _("Connecting to %s:%hu... "),
-                  pretty_addr, port);
+      const ip_address *ip = address_list_address_at (al, i);
+      sock = connect_to_ip (ip, port, host);
+      if (sock >= 0)
+       {
+         /* Success. */
+         address_list_set_connected (al);
+         address_list_release (al);
+         return sock;
+       }
+
+      /* The attempt to connect has failed.  Continue with the loop
+        and try next address. */
+
+      address_list_set_faulty (al, i);
     }
 
-  /* Make an internet socket, stream type.  */
-  sock = socket (ip_default_family, SOCK_STREAM, 0);
+  /* Failed to connect to any of the addresses in AL. */
+
+  if (address_list_connected_p (al))
+    {
+      /* We connected to AL before, but cannot do so now.  That might
+        indicate that our DNS cache entry for HOST has expired.  */
+      address_list_release (al);
+      al = lookup_host (host, LH_REFRESH);
+      goto retry;
+    }
+  address_list_release (al);
+
+  return -1;
+}
+\f
+/* 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.
+
+   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;
+  struct sockaddr_storage ss;
+  struct sockaddr *sa = (struct sockaddr *)&ss;
+
+  /* For setting options with setsockopt. */
+  int setopt_val = 1;
+  void *setopt_ptr = (void *)&setopt_val;
+  socklen_t setopt_size = sizeof (setopt_val);
+
+#ifdef ENABLE_IPV6
+  if (bind_address->type == IPV6_ADDRESS) 
+    family = AF_INET6;
+#endif
+
+  sock = socket (family, SOCK_STREAM, 0);
   if (sock < 0)
-    goto out;
+    return -1;
+
+#ifdef SO_REUSEADDR
+  setsockopt (sock, SOL_SOCKET, SO_REUSEADDR, setopt_ptr, setopt_size);
+#endif
+
+#ifdef ENABLE_IPV6
+# ifdef HAVE_IPV6_V6ONLY
+  if (family == AF_INET6)
+    setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, setopt_ptr, setopt_size);
+# endif
+#endif
+
+  xzero (ss);
+  sockaddr_set_data (sa, bind_address, *port);
+  if (bind (sock, sa, sockaddr_size (sa)) < 0)
+    {
+      fd_close (sock);
+      return -1;
+    }
+  DEBUGP (("Local socket fd %d bound.\n", sock));
 
-  resolve_bind_address ();
-  if (bind_address_resolved)
+  /* If *PORT is 0, find out which port we've bound to.  */
+  if (*port == 0)
     {
-      /* 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 ()))
+      socklen_t sa_len = sockaddr_size (sa);
+      if (getsockname (sock, sa, &sa_len) < 0)
        {
-         close (sock);
-         sock = -1;
-         goto out;
+         /* 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.  */
+         fd_close (sock);
+         return -1;
        }
+      sockaddr_get_data (sa, NULL, port);
+      DEBUGP (("binding to address %s using port %i.\n", 
+              pretty_print_address (bind_address), *port));
     }
-
-  /* Connect the socket to the remote host.  */
-  if (connect_with_timeout (sock, &sa.sa, sockaddr_len (), opt.timeout) < 0)
+  if (listen (sock, 1) < 0)
     {
-      close (sock);
-      sock = -1;
-      goto out;
+      fd_close (sock);
+      return -1;
     }
+  return sock;
+}
+
+/* Like a call to accept(), but with the added check for timeout.
+
+   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().
+
+   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.  */
 
- out:
-  if (sock >= 0)
+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);
+
+  if (opt.connect_timeout)
     {
-      /* Success. */
-      if (!silent)
-       logprintf (LOG_VERBOSE, _("connected.\n"));
-      DEBUGP (("Created socket %d.\n", sock));
+      int test = select_fd (local_sock, opt.connect_timeout, WAIT_FOR_READ);
+      if (test == 0)
+       errno = ETIMEDOUT;
+      if (test <= 0)
+       return -1;
     }
+  sock = accept (local_sock, sa, &addrlen);
+  DEBUGP (("Accepted client at socket %d.\n", sock));
+  return sock;
+}
+
+/* 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
+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 (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)
     {
-      save_errno = errno;
-      if (!silent)
-       logprintf (LOG_VERBOSE, "failed: %s.\n", strerror (errno));
-      errno = save_errno;
+#ifdef ENABLE_IPV6
+    case AF_INET6:
+      {
+       struct sockaddr_in6 *sa6 = (struct sockaddr_in6 *)&storage;
+       ip->type = IPV6_ADDRESS;
+       ADDRESS_IPV6_IN6_ADDR (ip) = sa6->sin6_addr;
+#ifdef HAVE_SOCKADDR_IN6_SCOPE_ID
+       ADDRESS_IPV6_SCOPE (ip) = sa6->sin6_scope_id;
+#endif
+       DEBUGP (("conaddr is: %s\n", pretty_print_address (ip)));
+       return 1;
+      }
+#endif
+    case AF_INET:
+      {
+       struct sockaddr_in *sa = (struct sockaddr_in *)&storage;
+       ip->type = IPV4_ADDRESS;
+       ADDRESS_IPV4_IN_ADDR (ip) = sa->sin_addr;
+       DEBUGP (("conaddr is: %s\n", pretty_print_address (ip)));
+       return 1;
+      }
+    default:
+      abort ();
     }
 
-  return sock;
+  return 0;
 }
 
-/* Connect to a remote host whose address has been resolved. */
+/* Return non-zero if the error from the connect code can be
+   considered retryable.  Wget normally retries after errors, but the
+   exception are the "unsupported protocol" type errors (possible on
+   IPv4/IPv6 dual family systems) and "connection refused".  */
+
 int
-connect_to_many (struct address_list *al, unsigned short port, int silent)
+retryable_socket_connect_error (int err)
 {
-  int i, start, end;
+  /* Have to guard against some of these values not being defined.
+     Cannot use a switch statement because some of the values might be
+     equal.  */
+  if (0
+#ifdef EAFNOSUPPORT
+      || err == EAFNOSUPPORT
+#endif
+#ifdef EPFNOSUPPORT
+      || err == EPFNOSUPPORT
+#endif
+#ifdef ESOCKTNOSUPPORT         /* no, "sockt" is not a typo! */
+      || err == ESOCKTNOSUPPORT
+#endif
+#ifdef EPROTONOSUPPORT
+      || err == EPROTONOSUPPORT
+#endif
+#ifdef ENOPROTOOPT
+      || err == ENOPROTOOPT
+#endif
+      /* Apparently, older versions of Linux and BSD used EINVAL
+        instead of EAFNOSUPPORT and such.  */
+      || err == EINVAL
+      )
+    return 0;
 
-  address_list_get_bounds (al, &start, &end);
-  for (i = start; i < end; i++)
-    {
-      ip_address addr;
-      int sock;
-      address_list_copy_one (al, i, &addr);
+  if (err == ECONNREFUSED && !opt.retry_connrefused)
+    return 0;
 
-      sock = connect_to_one (&addr, port, silent);
-      if (sock >= 0)
-       /* Success. */
-       return sock;
+  return 1;
+}
 
-      address_list_set_faulty (al, i);
+#ifdef ENABLE_IPV6
+# ifndef HAVE_GETADDRINFO_AI_ADDRCONFIG
 
-      /* The attempt to connect has failed.  Continue with the loop
-        and try next address. */
+/* Return non-zero if the INET6 socket family is supported on the
+   system.
+
+   This doesn't guarantee that we're able to connect to IPv6 hosts,
+   but it's better than nothing.  It is only used on systems where
+   getaddrinfo doesn't support AI_ADDRCONFIG.  (See lookup_host.)  */
+
+int
+socket_has_inet6 (void)
+{
+  static int supported = -1;
+  if (supported == -1)
+    {
+      int sock = socket (AF_INET6, SOCK_STREAM, 0);
+      if (sock < 0)
+       supported = 0;
+      else
+       {
+         fd_close (sock);
+         supported = 1;
+       }
     }
+  return supported;
+}
 
-  return -1;
+# endif/* not HAVE_GETADDRINFO_AI_ADDRCONFIG */
+#endif /* ENABLE_IPV6 */
+
+/* Wait for a single descriptor to become available, 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_FOR_READ and WAIT_FOR_WRITE.
+
+   This is a mere convenience wrapper around the select call, and
+   should be taken as such (for example, it doesn't implement Wget's
+   0-timeout-means-no-timeout semantics.)  */
+
+int
+select_fd (int fd, double maxtime, int wait_for)
+{
+#ifdef HAVE_SELECT
+  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;
+
+#else  /* not HAVE_SELECT */
+
+  /* If select() unavailable, just return 1.  In most usages in Wget,
+     this is the appropriate response -- "if we can't poll, go ahead
+     with the blocking operation".  If a specific part of code needs
+     different behavior, it can use #ifdef HAVE_SELECT to test whether
+     polling really occurs.  */
+  return 1;
+
+#endif /* not HAVE_SELECT */
 }
 
 int
@@ -273,210 +695,252 @@ test_socket_open (int sock)
   return 1;
 #endif
 }
+\f
+/* Basic socket operations, mostly EINTR wrappers.  */
 
-/* Bind the local port PORT.  This does all the necessary work, which
-   is creating a socket, setting SO_REUSEADDR option on it, then
-   calling bind() and listen().  If *PORT is 0, a random port is
-   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)
-{
-  int optval = 1;
-  wget_sockaddr srv;
-  memset (&srv, 0, sizeof (wget_sockaddr));
-
-  msock = -1;
-
-  if ((msock = socket (family, SOCK_STREAM, 0)) < 0)
-    return CONSOCKERR;
-  if (setsockopt (msock, SOL_SOCKET, SO_REUSEADDR,
-                 (char *)&optval, sizeof (optval)) < 0)
-    return CONSOCKERR;
-
-  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)
-    {
-      CLOSE (msock);
-      msock = -1;
-      return BINDERR;
-    }
-  DEBUGP (("Master socket fd %d bound.\n", msock));
-  if (!*port)
-    {
-      /* #### addrlen should be a 32-bit type, which int is not
-         guaranteed to be.  Oh, and don't try to make it a size_t,
-         because that can be 64-bit.  */
-      int sa_len = sockaddr_len ();
-      if (getsockname (msock, &srv.sa, &sa_len) < 0)
-       {
-         CLOSE (msock);
-         msock = -1;
-         return CONPORTERR;
-       }
-      *port = wget_sockaddr_get_port (&srv);
-      DEBUGP (("using port %i.\n", *port));
-    }
-  if (listen (msock, 1) < 0)
-    {
-      CLOSE (msock);
-      msock = -1;
-      return LISTENERR;
-    }
-  return BINDOK;
-}
-
-#ifdef HAVE_SELECT
-/* Wait for file descriptor FD to be available, timing out after
-   MAXTIME seconds.  "Available" means readable if writep is 0,
-   writeable otherwise.
+#ifdef WINDOWS
+# define read(fd, buf, cnt) recv (fd, buf, cnt, 0)
+# define write(fd, buf, cnt) send (fd, buf, cnt, 0)
+# define close(fd) closesocket (fd)
+#endif
 
-   Returns 1 if FD is available, 0 for timeout and -1 for error.  */
+#ifdef __BEOS__
+# define read(fd, buf, cnt) recv (fd, buf, cnt, 0)
+# define write(fd, buf, cnt) send (fd, buf, cnt, 0)
+#endif
 
-int
-select_fd (int fd, int maxtime, int writep)
+static int
+sock_read (int fd, char *buf, int bufsize)
 {
-  fd_set fds;
-  fd_set *rd = NULL, *wrt = NULL;
-  struct timeval tmout;
-  int result;
-
-  FD_ZERO (&fds);
-  FD_SET (fd, &fds);
-  *(writep ? &wrt : &rd) = &fds;
-
-  tmout.tv_sec = maxtime;
-  tmout.tv_usec = 0;
-
+  int res;
   do
-    result = select (fd + 1, rd, wrt, NULL, &tmout);
-  while (result < 0 && errno == EINTR);
-
-  /* When we've timed out, set errno to ETIMEDOUT for the convenience
-     of the caller. */
-  if (result == 0)
-    errno = ETIMEDOUT;
+    res = read (fd, buf, bufsize);
+  while (res == -1 && errno == EINTR);
+  return res;
+}
 
-  return result;
+static int
+sock_write (int fd, char *buf, int bufsize)
+{
+  int res;
+  do
+    res = write (fd, buf, bufsize);
+  while (res == -1 && errno == EINTR);
+  return res;
 }
-#endif /* HAVE_SELECT */
-
-/* Call accept() on MSOCK and store the result to *SOCK.  This assumes
-   that bindport() has been used to initialize MSOCK to a correct
-   value.  It blocks the caller until a connection is established.  If
-   no connection is established for OPT.TIMEOUT seconds, the function
-   exits with an error status.  */
-uerr_t
-acceptport (int *sock)
+
+static int
+sock_poll (int fd, double timeout, int wait_for)
 {
-  int addrlen = sockaddr_len ();
+  return select_fd (fd, timeout, wait_for);
+}
 
-#ifdef HAVE_SELECT
-  if (select_fd (msock, opt.timeout, 0) <= 0)
-    return ACCEPTERR;
-#endif
-  if ((*sock = accept (msock, addr, &addrlen)) < 0)
-    return ACCEPTERR;
-  DEBUGP (("Created socket fd %d.\n", *sock));
-  return ACCEPTOK;
+static int
+sock_peek (int fd, char *buf, int bufsize)
+{
+  int res;
+  do
+    res = recv (fd, buf, bufsize, MSG_PEEK);
+  while (res == -1 && errno == EINTR);
+  return res;
 }
 
-/* Close SOCK, as well as the most recently remembered MSOCK, created
-   via bindport().  If SOCK is -1, close MSOCK only.  */
-void
-closeport (int sock)
+static void
+sock_close (int fd)
 {
-  /*shutdown (sock, 2);*/
-  if (sock != -1)
-    CLOSE (sock);
-  if (msock != -1)
-    CLOSE (msock);
-  msock = -1;
+  close (fd);
+  DEBUGP (("Closed fd %d\n", fd));
 }
+#undef read
+#undef write
+#undef close
+\f
+/* Reading and writing from the network.  We build around the socket
+   (file descriptor) API, but support "extended" operations for things
+   that are not mere file descriptors under the hood, such as SSL
+   sockets.
+
+   That way the user code can call fd_read(fd, ...) and we'll run read
+   or SSL_read or whatever is necessary.  */
+
+static struct hash_table *transport_map;
+static int transport_map_modified_tick;
+
+struct transport_info {
+  fd_reader_t reader;
+  fd_writer_t writer;
+  fd_poller_t poller;
+  fd_peeker_t peeker;
+  fd_closer_t closer;
+  void *ctx;
+};
 
-/* Return the local IP address associated with the connection on FD.  */
+/* Register the transport layer operations that will be used when
+   reading, writing, and polling FD.
 
-int
-conaddr (int fd, ip_address *ip)
-{
-  wget_sockaddr mysrv;
+   This should be used for transport layers like SSL that piggyback on
+   sockets.  FD should otherwise be a real socket, on which you can
+   call getpeername, etc.  */
 
-  /* see bindport() for discussion of using `int' here. */
-  int addrlen = sizeof (mysrv);        
+void
+fd_register_transport (int fd, fd_reader_t reader, fd_writer_t writer,
+                      fd_poller_t poller, fd_peeker_t peeker,
+                      fd_closer_t closer, void *ctx)
+{
+  struct transport_info *info;
+
+  /* The file descriptor must be non-negative to be registered.
+     Negative values are ignored by fd_close(), and -1 cannot be used as
+     hash key.  */
+  assert (fd >= 0);
+
+  info = xnew (struct transport_info);
+  info->reader = reader;
+  info->writer = writer;
+  info->poller = poller;
+  info->peeker = peeker;
+  info->closer = closer;
+  info->ctx = ctx;
+  if (!transport_map)
+    transport_map = hash_table_new (0, NULL, NULL);
+  hash_table_put (transport_map, (void *) fd, info);
+  ++transport_map_modified_tick;
+}
 
-  if (getsockname (fd, &mysrv.sa, (int *)&addrlen) < 0)
-    return 0;
+/* When fd_read/fd_write are called multiple times in a loop, they should
+   remember the INFO pointer instead of fetching it every time.  It is
+   not enough to compare FD to LAST_FD because FD might have been
+   closed and reopened.  modified_tick ensures that changes to
+   transport_map will not be unnoticed.
+
+   This is a macro because we want the static storage variables to be
+   per-function.  */
+
+#define LAZY_RETRIEVE_INFO(info) do {                                  \
+  static struct transport_info *last_info;                             \
+  static int last_fd = -1, last_tick;                                  \
+  if (!transport_map)                                                  \
+    info = NULL;                                                       \
+  else if (last_fd == fd && last_tick == transport_map_modified_tick)  \
+    info = last_info;                                                  \
+  else                                                                 \
+    {                                                                  \
+      info = hash_table_get (transport_map, (void *) fd);              \
+      last_fd = fd;                                                    \
+      last_info = info;                                                        \
+      last_tick = transport_map_modified_tick;                         \
+    }                                                                  \
+} while (0)
 
-  switch (mysrv.sa.sa_family)
+static int
+poll_internal (int fd, struct transport_info *info, int wf, double timeout)
+{
+  if (timeout == -1)
+    timeout = opt.read_timeout;
+  if (timeout)
     {
-#ifdef INET6
-    case AF_INET6:
-      memcpy (ip, &mysrv.sin6.sin6_addr, 16);
-      return 1;
-#endif
-    case AF_INET:
-      map_ipv4_to_ip ((ip4_address *)&mysrv.sin.sin_addr, ip);
-      return 1;
-    default:
-      abort ();
+      int test;
+      if (info && info->poller)
+       test = info->poller (fd, timeout, wf, info->ctx);
+      else
+       test = sock_poll (fd, timeout, wf);
+      if (test == 0)
+       errno = ETIMEDOUT;
+      if (test <= 0)
+       return 0;
     }
-  return 0;
+  return 1;
 }
 
-/* Read at most LEN bytes from FD, storing them to BUF.  This is
-   virtually the same as read(), but takes care of EINTR braindamage
-   and uses select() to timeout the stale connections (a connection is
-   stale if more than OPT.TIMEOUT time is spent in select() or
-   read()).  */
+/* Read no more than BUFSIZE bytes of data from FD, storing them to
+   BUF.  If TIMEOUT is non-zero, the operation aborts if no data is
+   received after that many seconds.  If TIMEOUT is -1, the value of
+   opt.timeout is used for TIMEOUT.  */
 
 int
-iread (int fd, char *buf, int len)
+fd_read (int fd, char *buf, int bufsize, double timeout)
 {
-  int res;
+  struct transport_info *info;
+  LAZY_RETRIEVE_INFO (info);
+  if (!poll_internal (fd, info, WAIT_FOR_READ, timeout))
+    return -1;
+  if (info && info->reader)
+    return info->reader (fd, buf, bufsize, info->ctx);
+  else
+    return sock_read (fd, buf, bufsize);
+}
 
-#ifdef HAVE_SELECT
-  if (opt.timeout)
-    if (select_fd (fd, opt.timeout, 0) <= 0)
-      return -1;
-#endif
-  do
-    res = READ (fd, buf, len);
-  while (res == -1 && errno == EINTR);
+/* The same as xread, but don't actually read the data, just copy it
+   instead.  */
 
-  return res;
+int
+fd_peek (int fd, char *buf, int bufsize, double timeout)
+{
+  struct transport_info *info;
+  LAZY_RETRIEVE_INFO (info);
+  if (!poll_internal (fd, info, WAIT_FOR_READ, timeout))
+    return -1;
+  if (info && info->peeker)
+    return info->peeker (fd, buf, bufsize, info->ctx);
+  else
+    return sock_peek (fd, buf, bufsize);
 }
 
-/* Write LEN bytes from BUF to FD.  This is similar to iread(), but
-   unlike iread(), it makes sure that all of BUF is actually written
-   to FD, so callers needn't bother with checking that the return
-   value equals to LEN.  Instead, you should simply check for -1.  */
+/* Write the entire contents of BUF to FD.  If TIMEOUT is non-zero,
+   the operation aborts if no data is received after that many
+   seconds.  If TIMEOUT is -1, the value of opt.timeout is used for
+   TIMEOUT.  */
 
 int
-iwrite (int fd, char *buf, int len)
+fd_write (int fd, char *buf, int bufsize, double timeout)
 {
-  int res = 0;
+  int res;
+  struct transport_info *info;
+  LAZY_RETRIEVE_INFO (info);
 
-  /* `write' may write less than LEN bytes, thus the outward loop
-     keeps trying it until all was written, or an error occurred.  The
-     inner loop is reserved for the usual EINTR f*kage, and the
-     innermost loop deals with the same during select().  */
-  while (len > 0)
+  /* `write' may write less than LEN bytes, thus the loop keeps trying
+     it until all was written, or an error occurred.  */
+  res = 0;
+  while (bufsize > 0)
     {
-#ifdef HAVE_SELECT
-      if (opt.timeout)
-       if (select_fd (fd, opt.timeout, 1) <= 0)
-         return -1;
-#endif
-      do
-       res = WRITE (fd, buf, len);
-      while (res == -1 && errno == EINTR);
+      if (!poll_internal (fd, info, WAIT_FOR_WRITE, timeout))
+       return -1;
+      if (info && info->writer)
+       res = info->writer (fd, buf, bufsize, info->ctx);
+      else
+       res = sock_write (fd, buf, bufsize);
       if (res <= 0)
        break;
       buf += res;
-      len -= res;
+      bufsize -= res;
     }
   return res;
 }
+
+/* Close the file descriptor FD.  */
+
+void
+fd_close (int fd)
+{
+  struct transport_info *info;
+  if (fd < 0)
+    return;
+
+  /* Don't use LAZY_RETRIEVE_INFO because fd_close() is only called once
+     per socket, so that particular optimization wouldn't work.  */
+  info = NULL;
+  if (transport_map)
+    info = hash_table_get (transport_map, (void *) fd);
+
+  if (info && info->closer)
+    info->closer (fd, info->ctx);
+  else
+    sock_close (fd);
+
+  if (info)
+    {
+      hash_table_remove (transport_map, (void *) fd);
+      xfree (info);
+      ++transport_map_modified_tick;
+    }
+}