Initial keep-alive support for the client socket.
[privoxy.git] / jbsockets.c
index 0693fe4..18e4246 100644 (file)
@@ -1,4 +1,4 @@
-const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.48 2008/09/04 08:13:58 fabiankeil Exp $";
+const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.54 2009/04/17 11:45:19 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jbsockets.c,v $
@@ -8,7 +8,7 @@ const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.48 2008/09/04 08:13:58 fabian
  *                OS-independent.  Contains #ifdefs to make this work
  *                on many platforms.
  *
- * Copyright   :  Written by and Copyright (C) 2001-2007 the SourceForge
+ * Copyright   :  Written by and Copyright (C) 2001-2009 the
  *                Privoxy team. http://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
@@ -35,6 +35,27 @@ const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.48 2008/09/04 08:13:58 fabian
  *
  * Revisions   :
  *    $Log: jbsockets.c,v $
+ *    Revision 1.54  2009/04/17 11:45:19  fabiankeil
+ *    Replace HAVE_GETADDRINFO and HAVE_GETNAMEINFO macros
+ *    with HAVE_RFC2553 macro. Original patch by Petr Pisar.
+ *
+ *    Revision 1.53  2009/04/17 11:39:52  fabiankeil
+ *    If the hostname is 'localhost' or not specified, request an AF_INET address.
+ *
+ *    Revision 1.52  2009/04/17 11:34:34  fabiankeil
+ *    Style cosmetics for the IPv6 code.
+ *
+ *    Revision 1.51  2009/04/17 11:27:49  fabiankeil
+ *    Petr Pisar's privoxy-3.0.12-ipv6-3.diff.
+ *
+ *    Revision 1.50  2008/12/20 14:53:55  fabiankeil
+ *    Add config option socket-timeout to control the time
+ *    Privoxy waits for data to arrive on a socket. Useful
+ *    in case of stale ssh tunnels or when fuzz-testing.
+ *
+ *    Revision 1.49  2008/11/10 17:03:57  fabiankeil
+ *    Fix a gcc44 warning and remove a now-obsolete cast.
+ *
  *    Revision 1.48  2008/09/04 08:13:58  fabiankeil
  *    Prepare for critical sections on Windows by adding a
  *    layer of indirection before the pthread mutex functions.
@@ -307,6 +328,16 @@ const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.48 2008/09/04 08:13:58 fabian
 
 #endif
 
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+#ifdef HAVE_POLL
+#ifdef __GLIBC__
+#include <sys/poll.h>
+#else
+#include <poll.h>
+#endif /* def __GLIBC__ */
+#endif /* HAVE_POLL */
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
 #include "project.h"
 
 #ifdef FEATURE_PTHREAD
@@ -317,6 +348,7 @@ const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.48 2008/09/04 08:13:58 fabian
 #include "jbsockets.h"
 #include "filters.h"
 #include "errlog.h"
+#include "miscutil.h"
 
 const char jbsockets_h_rcs[] = JBSOCKETS_H_VERSION;
 
@@ -347,6 +379,181 @@ const char jbsockets_h_rcs[] = JBSOCKETS_H_VERSION;
  *                file descriptor.
  *
  *********************************************************************/
+#ifdef HAVE_RFC2553
+/* Getaddrinfo implementation */
+jb_socket connect_to(const char *host, int portnum, struct client_state *csp)
+{
+   struct addrinfo hints, *result, *rp;
+   char service[6];
+   int retval;
+   jb_socket fd;
+   fd_set wfds;
+   struct timeval tv[1];
+#if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA)
+   int   flags;
+#endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) */
+   int connect_failed;
+
+#ifdef FEATURE_ACL
+   struct access_control_addr dst[1];
+#endif /* def FEATURE_ACL */
+
+   retval = snprintf(service, sizeof(service), "%d", portnum);
+   if ((-1 == retval) || (sizeof(service) <= retval))
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "Port number (%d) ASCII decimal representation doesn't fit into 6 bytes",
+         portnum);
+      csp->http->host_ip_addr_str = strdup("unknown");
+      return(JB_INVALID_SOCKET);
+   }
+
+   memset((char *)&hints, 0, sizeof(hints));
+   hints.ai_family = AF_UNSPEC;
+   hints.ai_socktype = SOCK_STREAM;
+   hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; /* avoid service look-up */
+   if ((retval = getaddrinfo(host, service, &hints, &result)))
+   {
+      log_error(LOG_LEVEL_INFO,
+         "Can not resolve %s: %s", host, gai_strerror(retval));
+      csp->http->host_ip_addr_str = strdup("unknown");
+      return(JB_INVALID_SOCKET);
+   }
+
+   for (rp = result; rp != NULL; rp = rp->ai_next)
+   {
+
+#ifdef FEATURE_ACL
+      memcpy(&dst->addr, rp->ai_addr, rp->ai_addrlen);
+
+      if (block_acl(dst, csp))
+      {
+#ifdef __OS2__
+         errno = SOCEPERM;
+#else
+         errno = EPERM;
+#endif
+         continue;
+      }
+#endif /* def FEATURE_ACL */
+
+      csp->http->host_ip_addr_str = malloc(NI_MAXHOST);
+      retval = getnameinfo(rp->ai_addr, rp->ai_addrlen,
+         csp->http->host_ip_addr_str, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+      if (!csp->http->host_ip_addr_str || retval)
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Can not save csp->http->host_ip_addr_str: %s",
+            (csp->http->host_ip_addr_str) ?
+            gai_strerror(retval) : "Insufficient memory");
+         freez(csp->http->host_ip_addr_str);
+         continue;
+      }
+
+#ifdef _WIN32
+      if ((fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) ==
+            JB_INVALID_SOCKET)
+#else
+      if ((fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) < 0)
+#endif
+      {
+         continue;
+      }
+
+#ifdef TCP_NODELAY
+      {  /* turn off TCP coalescence */
+         int mi = 1;
+         setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (char *) &mi, sizeof (int));
+      }
+#endif /* def TCP_NODELAY */
+
+#if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__)
+      if ((flags = fcntl(fd, F_GETFL, 0)) != -1)
+      {
+         flags |= O_NDELAY;
+         fcntl(fd, F_SETFL, flags);
+      }
+#endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) */
+
+      connect_failed = 0;
+      while (connect(fd, rp->ai_addr, rp->ai_addrlen) == JB_INVALID_SOCKET)
+      {
+#ifdef _WIN32
+         if (errno == WSAEINPROGRESS)
+#elif __OS2__
+         if (sock_errno() == EINPROGRESS)
+#else /* ifndef _WIN32 */
+         if (errno == EINPROGRESS)
+#endif /* ndef _WIN32 || __OS2__ */
+         {
+            break;
+         }
+
+#ifdef __OS2__
+         if (sock_errno() != EINTR)
+#else
+         if (errno != EINTR)
+#endif /* __OS2__ */
+         {
+            close_socket(fd);
+            connect_failed = 1;
+            break;
+         }
+      }
+      if (connect_failed)
+      {
+         continue;
+      }
+
+#if !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__)
+      if (flags != -1)
+      {
+         flags &= ~O_NDELAY;
+         fcntl(fd, F_SETFL, flags);
+      }
+#endif /* !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__) */
+
+      /* wait for connection to complete */
+      FD_ZERO(&wfds);
+      FD_SET(fd, &wfds);
+
+      tv->tv_sec  = 30;
+      tv->tv_usec = 0;
+
+      /* MS Windows uses int, not SOCKET, for the 1st arg of select(). Wierd! */
+      if (select((int)fd + 1, NULL, &wfds, NULL, tv) <= 0)
+      {
+         close_socket(fd);
+         continue;
+      }
+
+      break; /* for; Connection established; don't try other addresses */
+   }
+
+   freeaddrinfo(result);
+   if (!rp)
+   {
+      log_error(LOG_LEVEL_INFO,
+         "Could not connect to TCP/[%s]:%s", host, service);
+      return(JB_INVALID_SOCKET);
+   }
+   /*
+    * XXX: Current connection verification (EINPROGRESS && select()
+    * for writing) is not sufficient. E.g. on Linux-2.6.27 with glibc-2.6
+    * select returns socket ready for writing, however subsequential
+    * write(2) fails with ENOCONNECT. Read Linux connect(2) man page
+    * about non-blocking sockets.
+    * Thus we can't log here that the socket is connected.
+    */
+   /* log_error(LOG_LEVEL_INFO, "Connected to TCP/[%s]:%s", host, service); */
+
+   return(fd);
+
+}
+
+#else /* ndef HAVE_RFC2553 */
+/* Pre-getaddrinfo implementation */
+
 jb_socket connect_to(const char *host, int portnum, struct client_state *csp)
 {
    struct sockaddr_in inaddr;
@@ -474,6 +681,7 @@ jb_socket connect_to(const char *host, int portnum, struct client_state *csp)
    return(fd);
 
 }
+#endif /* ndef HAVE_RFC2553 */
 
 
 /*********************************************************************
@@ -583,6 +791,46 @@ int read_socket(jb_socket fd, char *buf, int len)
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  data_is_available
+ *
+ * Description :  Waits for data to arrive on a socket.
+ *
+ * Parameters  :
+ *          1  :  fd = file descriptor of the socket to read
+ *          2  :  seconds_to_wait = number of seconds after which we give up.
+ *
+ * Returns     :  TRUE if data arrived in time,
+ *                FALSE otherwise.
+ *
+ *********************************************************************/
+int data_is_available(jb_socket fd, int seconds_to_wait)
+{
+   fd_set rfds;
+   struct timeval timeout;
+   int n;
+
+   memset(&timeout, 0, sizeof(timeout));
+   timeout.tv_sec = seconds_to_wait;
+
+#ifdef __OS2__
+   /* Copy and pasted from jcc.c ... */
+   memset(&rfds, 0, sizeof(fd_set));
+#else
+   FD_ZERO(&rfds);
+#endif
+   FD_SET(fd, &rfds);
+
+   n = select(fd+1, &rfds, NULL, NULL, &timeout);
+
+   /*
+    * XXX: Do we care about the different error conditions?
+    */
+   return (n == 1);
+}
+
+
 /*********************************************************************
  *
  * Function    :  close_socket
@@ -629,7 +877,19 @@ void close_socket(jb_socket fd)
  *********************************************************************/
 int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
 {
+#ifdef HAVE_RFC2553
+   struct addrinfo hints;
+   struct addrinfo *result, *rp;
+   /*
+    * XXX: portnum should be a string to allow symbolic service
+    * names in the configuration file and to avoid the following
+    * int2string.
+    */
+   char servnam[6];
+   int retval;
+#else
    struct sockaddr_in inaddr;
+#endif /* def HAVE_RFC2553 */
    jb_socket fd;
 #ifndef _WIN32
    int one = 1;
@@ -637,6 +897,45 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
 
    *pfd = JB_INVALID_SOCKET;
 
+#ifdef HAVE_RFC2553
+   retval = snprintf(servnam, sizeof(servnam), "%d", portnum);
+   if ((-1 == retval) || (sizeof(servnam) <= retval))
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "Port number (%d) ASCII decimal representation doesn't fit into 6 bytes",
+         portnum);
+      return -1;
+   }
+
+   memset(&hints, 0, sizeof(struct addrinfo));
+   if ((hostnam == NULL) || !strcmpic(hostnam, "localhost"))
+   {
+      /*
+       * XXX: This is a hack. The right thing to do
+       * would be to bind to both AF_INET and AF_INET6.
+       * This will also fail if there is no AF_INET
+       * version available.
+       */
+      hints.ai_family = AF_INET;
+   }
+   else
+   {
+      hints.ai_family = AF_UNSPEC;
+   }
+   hints.ai_socktype = SOCK_STREAM;
+   hints.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+   hints.ai_protocol = 0; /* Realy any stream protocol or TCP only */
+   hints.ai_canonname = NULL;
+   hints.ai_addr = NULL;
+   hints.ai_next = NULL;
+
+   if ((retval = getaddrinfo(hostnam, servnam, &hints, &result)))
+   {
+      log_error(LOG_LEVEL_ERROR,
+         "Can not resolve %s: %s", hostnam, gai_strerror(retval));
+      return -2;
+   }
+#else
    memset((char *)&inaddr, '\0', sizeof inaddr);
 
    inaddr.sin_family      = AF_INET;
@@ -659,8 +958,15 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
       inaddr.sin_port = htonl((unsigned long) portnum);
    }
 #endif /* ndef _WIN32 */
+#endif /* def HAVE_RFC2553 */
 
+#ifdef HAVE_RFC2553
+   for (rp = result; rp != NULL; rp = rp->ai_next)
+   {
+      fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
+#else
    fd = socket(AF_INET, SOCK_STREAM, 0);
+#endif /* def HAVE_RFC2553 */
 
 #ifdef _WIN32
    if (fd == JB_INVALID_SOCKET)
@@ -668,7 +974,11 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
    if (fd < 0)
 #endif
    {
+#ifdef HAVE_RFC2553
+      continue;
+#else
       return(-1);
+#endif
    }
 
 #ifndef _WIN32
@@ -687,7 +997,11 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&one, sizeof(one));
 #endif /* ndef _WIN32 */
 
+#ifdef HAVE_RFC2553
+   if (bind(fd, rp->ai_addr, rp->ai_addrlen) < 0)
+#else
    if (bind(fd, (struct sockaddr *)&inaddr, sizeof(inaddr)) < 0)
+#endif
    {
 #ifdef _WIN32
       errno = WSAGetLastError();
@@ -696,15 +1010,41 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
       if (errno == EADDRINUSE)
 #endif
       {
+#ifdef HAVE_RFC2553
+         freeaddrinfo(result);
+#endif
          close_socket(fd);
          return(-3);
       }
       else
       {
          close_socket(fd);
+#ifndef HAVE_RFC2553
          return(-1);
       }
    }
+#else
+      }
+   }
+   else
+   {
+      /* bind() succeeded, escape from for-loop */
+      /*
+       * XXX: Support multiple listening sockets (e.g. localhost
+       * resolves to AF_INET and AF_INET6, but only the first address
+       * is used
+       */
+      break;
+   }
+   }
+
+   freeaddrinfo(result);
+   if (rp == NULL)
+   {
+      /* All bind()s failed */
+      return(-1);
+   }
+#endif /* ndef HAVE_RFC2553 */
 
    while (listen(fd, MAX_LISTEN_BACKLOG) == -1)
    {
@@ -744,14 +1084,20 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
  *********************************************************************/
 void get_host_information(jb_socket afd, char **ip_address, char **hostname)
 {
+#ifdef HAVE_RFC2553
+   struct sockaddr_storage server;
+   int retval;
+#else
    struct sockaddr_in server;
    struct hostent *host = NULL;
+#endif /* HAVE_RFC2553 */
 #if defined(_WIN32) || defined(__OS2__) || defined(__APPLE_CC__) || defined(AMIGA)
    /* according to accept_connection() this fixes a warning. */
-   int s_length;
+   int s_length, s_length_provided;
 #else
-   socklen_t s_length;
+   socklen_t s_length, s_length_provided;
 #endif
+#ifndef HAVE_RFC2553
 #if defined(HAVE_GETHOSTBYADDR_R_8_ARGS) ||  defined(HAVE_GETHOSTBYADDR_R_7_ARGS) || defined(HAVE_GETHOSTBYADDR_R_5_ARGS)
    struct hostent result;
 #if defined(HAVE_GETHOSTBYADDR_R_5_ARGS)
@@ -761,7 +1107,8 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname)
    int thd_err;
 #endif /* def HAVE_GETHOSTBYADDR_R_5_ARGS */
 #endif /* def HAVE_GETHOSTBYADDR_R_(8|7|5)_ARGS */
-   s_length = sizeof(server);
+#endif /* ifndef HAVE_RFC2553 */
+   s_length = s_length_provided = sizeof(server);
 
    if (NULL != hostname)
    {
@@ -771,8 +1118,25 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname)
 
    if (!getsockname(afd, (struct sockaddr *) &server, &s_length))
    {
+      if (s_length > s_length_provided)
+      {
+         log_error(LOG_LEVEL_ERROR, "getsockname() truncated server address");
+         return;
+      }
+#ifdef HAVE_RFC2553
+      *ip_address = malloc(NI_MAXHOST);
+      retval = getnameinfo((struct sockaddr *) &server, s_length,
+         *ip_address, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+      if (retval)
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Unable to print my own IP address: %s", gai_strerror(retval));
+         freez(*ip_address);
+         return;
+      }
+#else
       *ip_address = strdup(inet_ntoa(server.sin_addr));
-
+#endif /* HAVE_RFC2553 */
       if (NULL == hostname)
       {
          /*
@@ -781,6 +1145,18 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname)
           */
          return;
       }
+
+#ifdef HAVE_RFC2553
+      *hostname = malloc(NI_MAXHOST);
+      retval = getnameinfo((struct sockaddr *) &server, s_length,
+         *hostname, NI_MAXHOST, NULL, 0, NI_NAMEREQD);
+      if (retval)
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Unable to resolve my own IP address: %s", gai_strerror(retval));
+         freez(*hostname);
+      }
+#else
 #if defined(HAVE_GETHOSTBYADDR_R_8_ARGS)
       gethostbyaddr_r((const char *)&server.sin_addr,
                       sizeof(server.sin_addr), AF_INET,
@@ -818,6 +1194,7 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname)
       {
          *hostname = strdup(host->h_name);
       }
+#endif /* else def HAVE_RFC2553 */
    }
 
    return;
@@ -842,7 +1219,13 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname)
  *********************************************************************/
 int accept_connection(struct client_state * csp, jb_socket fd)
 {
+#ifdef HAVE_RFC2553
+   /* XXX: client is stored directly into csp->tcp_addr */
+#define client (csp->tcp_addr)
+   int retval;
+#else
    struct sockaddr_in client;
+#endif
    jb_socket afd;
 #if defined(_WIN32) || defined(__OS2__) || defined(__APPLE_CC__) || defined(AMIGA)
    /* Wierdness - fix a warning. */
@@ -871,8 +1254,21 @@ int accept_connection(struct client_state * csp, jb_socket fd)
 #endif
 
    csp->cfd = afd;
+#ifdef HAVE_RFC2553
+   csp->ip_addr_str = malloc(NI_MAXHOST);
+   retval = getnameinfo((struct sockaddr *) &client, c_length,
+         csp->ip_addr_str, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+   if (!csp->ip_addr_str || retval)
+   {
+      log_error(LOG_LEVEL_ERROR, "Can not save csp->ip_addr_str: %s",
+            (csp->ip_addr_str) ? gai_strerror(retval) : "Insuffcient memory");
+      freez(csp->ip_addr_str);
+   }
+#undef client
+#else
    csp->ip_addr_str  = strdup(inet_ntoa(client.sin_addr));
    csp->ip_addr_long = ntohl(client.sin_addr.s_addr);
+#endif /* def HAVE_RFC2553 */
 
    return 1;
 
@@ -999,6 +1395,68 @@ unsigned long resolve_hostname_to_ip(const char *host)
 }
 
 
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+/*********************************************************************
+ *
+ * Function    :  socket_is_still_usable
+ *
+ * Description :  Decides whether or not an open socket is still usable.
+ *
+ * Parameters  :
+ *          1  :  sfd = The socket to check.
+ *
+ * Returns     :  TRUE for yes, otherwise FALSE.
+ *
+ *********************************************************************/
+int socket_is_still_usable(jb_socket sfd)
+{
+#ifdef HAVE_POLL
+   int poll_result;
+   struct pollfd poll_fd[1];
+
+   memset(poll_fd, 0, sizeof(poll_fd));
+   poll_fd[0].fd = sfd;
+   poll_fd[0].events = POLLIN;
+
+   poll_result = poll(poll_fd, 1, 0);
+
+   if (-1 != poll_result)
+   {
+      return !(poll_fd[0].revents & POLLIN);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_CONNECT, "Polling socket %d failed.", sfd);
+      return FALSE;
+   }
+#else
+   fd_set readable_fds;
+   struct timeval timeout;
+   int ret;
+   int socket_is_alive = 0;
+
+   memset(&timeout, '\0', sizeof(timeout));
+   FD_ZERO(&readable_fds);
+   FD_SET(sfd, &readable_fds);
+
+   ret = select((int)sfd+1, &readable_fds, NULL, NULL, &timeout);
+   if (ret < 0)
+   {
+      log_error(LOG_LEVEL_ERROR, "select() failed!: %E");
+   }
+
+   /*
+    * XXX: I'm not sure why !FD_ISSET() works,
+    * but apparently it does.
+    */
+   socket_is_alive = !FD_ISSET(sfd, &readable_fds);
+
+   return socket_is_alive;
+#endif /* def HAVE_POLL */
+}
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+
 /*
   Local Variables:
   tab-width: 3