Sketch in library interface
[users/heiko/exim.git] / src / src / ip.c
index 63645f13942a1e3912cb740adff2412c777bbf60..b492b9da1566d0c03dcc8aec11e4356aa458749e 100644 (file)
@@ -1,10 +1,8 @@
-/* $Cambridge: exim/src/src/ip.c,v 1.3 2005/06/27 14:29:43 ph10 Exp $ */
-
 /*************************************************
 *     Exim - an Internet mail transport agent    *
 *************************************************/
 
-/* Copyright (c) University of Cambridge 1995 - 2005 */
+/* Copyright (c) University of Cambridge 1995 - 2014 */
 /* See the file NOTICE for conditions of use and distribution. */
 
 /* Functions for doing things with sockets. With the advent of IPv6 this has
@@ -165,14 +163,16 @@ return bind(sock, (struct sockaddr *)&sin, s_len);
 *************************************************/
 
 /* This function connects a socket to a remote address and port. The socket may
-or may not have previously been bound to a local interface.
+or may not have previously been bound to a local interface. The socket is not
+closed, even in cases of error. It is expected that the calling function, which
+created the socket, will be the one that closes it.
 
 Arguments:
   sock        the socket
   af          AF_INET6 or AF_INET for the socket type
   address     the remote address, in text form
   port        the remote port
-  timeout     a timeout
+  timeout     a timeout (zero for indefinite timeout)
 
 Returns:      0 on success; -1 on failure, with errno set
 */
@@ -224,17 +224,11 @@ alarm(0);
 
 /* There is a testing facility for simulating a connection timeout, as I
 can't think of any other way of doing this. It converts a connection refused
-into a timeout.
-
-I had to add a second fudge to keep the tests working. Attempts to connect to
-10.x.x.x are expected to timeout, but sometimes they now give "No route to
-host". */
+into a timeout if the timeout is set to 999999. */
 
 if (running_in_test_harness)
   {
-  if ((save_errno == ECONNREFUSED && timeout == 999999) ||
-      (save_errno == EHOSTUNREACH && timeout > 0 &&
-         Ustrncmp(address, "10.", 3) == 0))
+  if (save_errno == ECONNREFUSED && timeout == 999999)
     {
     rc = -1;
     save_errno = EINTR;
@@ -249,13 +243,117 @@ if (rc >= 0) return 0;
 /* A failure whose error code is "Interrupted system call" is in fact
 an externally applied timeout if the signal handler has been run. */
 
-(void)close(sock);
 errno = (save_errno == EINTR && sigalrm_seen)? ETIMEDOUT : save_errno;
 return -1;
 }
 
 
 
+/*************************************************
+*    Create connected socket to remote host      *
+*************************************************/
+
+/* Create a socket and connect to host (name or number, ipv6 ok)
+   at one of port-range.
+
+Arguments:
+  type          SOCK_DGRAM or SOCK_STREAM
+  af            AF_INET6 or AF_INET for the socket type
+  address       the remote address, in text form
+  portlo,porthi the remote port range
+  timeout       a timeout
+  connhost     if not NULL, host_item filled in with connection details
+  errstr        pointer for allocated string on error
+
+Return:
+  socket fd, or -1 on failure (having allocated an error string)
+*/
+int
+ip_connectedsocket(int type, const uschar * hostname, int portlo, int porthi,
+       int timeout, host_item * connhost, uschar ** errstr)
+{
+int namelen, port;
+host_item shost;
+host_item *h;
+int af = 0, fd, fd4 = -1, fd6 = -1;
+
+shost.next = NULL;
+shost.address = NULL;
+shost.port = portlo;
+shost.mx = -1;
+
+namelen = Ustrlen(hostname);
+
+/* Anything enclosed in [] must be an IP address. */
+
+if (hostname[0] == '[' &&
+    hostname[namelen - 1] == ']')
+  {
+  uschar * host = string_copy(hostname);
+  host[namelen - 1] = 0;
+  host++;
+  if (string_is_ip_address(host, NULL) == 0)
+    {
+    *errstr = string_sprintf("malformed IP address \"%s\"", hostname);
+    return -1;
+    }
+  shost.name = shost.address = host;
+  }
+
+/* Otherwise check for an unadorned IP address */
+
+else if (string_is_ip_address(hostname, NULL) != 0)
+  shost.name = shost.address = string_copy(hostname);
+
+/* Otherwise lookup IP address(es) from the name */
+
+else
+  {
+  shost.name = string_copy(hostname);
+  if (host_find_byname(&shost, NULL, HOST_FIND_QUALIFY_SINGLE, NULL,
+      FALSE) != HOST_FOUND)
+    {
+    *errstr = string_sprintf("no IP address found for host %s", shost.name);
+    return -1;
+    }
+  }
+
+/* Try to connect to the server - test each IP till one works */
+
+for (h = &shost; h != NULL; h = h->next)
+  {
+  fd = (Ustrchr(h->address, ':') != 0)
+    ? (fd6 < 0) ? (fd6 = ip_socket(type, af = AF_INET6)) : fd6
+    : (fd4 < 0) ? (fd4 = ip_socket(type, af = AF_INET )) : fd4;
+
+  if (fd < 0)
+    {
+    *errstr = string_sprintf("failed to create socket: %s", strerror(errno));
+    goto bad;
+    }
+
+  for(port = portlo; port <= porthi; port++)
+    if (ip_connect(fd, af, h->address, port, timeout) == 0)
+      {
+      if (fd != fd6) close(fd6);
+      if (fd != fd4) close(fd4);
+      if (connhost) {
+       h->port = port;
+       *connhost = *h;
+       connhost->next = NULL;
+       }
+      return fd;
+      }
+  }
+
+*errstr = string_sprintf("failed to connect to %s: "
+  "couldn't connect to any host: %s", hostname, strerror(errno));
+
+bad:
+  close(fd4); close(fd6); return -1;
+}
+
+
 /*************************************************
 *         Set keepalive on a socket              *
 *************************************************/
@@ -305,7 +403,7 @@ ip_recv(int sock, uschar *buffer, int buffsize, int timeout)
 {
 fd_set select_inset;
 struct timeval tv;
-int start_recv = time(NULL);
+time_t start_recv = time(NULL);
 int rc;
 
 /* Wait until the socket is ready */
@@ -354,8 +452,10 @@ for (;;)
 close down of the connection), set errno to zero; otherwise leave it alone. */
 
 #ifdef SUPPORT_TLS
-if (tls_active == sock)
-  rc = tls_read(buffer, buffsize);
+if (tls_out.active == sock)
+  rc = tls_read(FALSE, buffer, buffsize);
+else if (tls_in.active == sock)
+  rc = tls_read(TRUE, buffer, buffsize);
 else
 #endif
   rc = recv(sock, buffer, buffsize, 0);
@@ -366,4 +466,183 @@ return -1;
 }
 
 
+
+
+/*************************************************
+*    Lookup address family of potential socket   *
+*************************************************/
+
+/* Given a file-descriptor, check to see if it's a socket and, if so,
+return the address family; detects IPv4 vs IPv6.  If not a socket then
+return -1.
+
+The value 0 is typically AF_UNSPEC, which should not be seen on a connected
+fd.  If the return is -1, the errno will be from getsockname(); probably
+ENOTSOCK or ECONNRESET.
+
+Arguments:     socket-or-not fd
+Returns:       address family or -1
+*/
+
+int
+ip_get_address_family(int fd)
+{
+struct sockaddr_storage ss;
+socklen_t sslen = sizeof(ss);
+
+if (getsockname(fd, (struct sockaddr *) &ss, &sslen) < 0)
+  return -1;
+
+return (int) ss.ss_family;
+}
+
+
+
+
+/*************************************************
+*       Lookup DSCP settings for a socket        *
+*************************************************/
+
+struct dscp_name_tableentry {
+  const uschar *name;
+  int value;
+};
+/* Keep both of these tables sorted! */
+static struct dscp_name_tableentry dscp_table[] = {
+#ifdef IPTOS_DSCP_AF11
+    { CUS"af11", IPTOS_DSCP_AF11 },
+    { CUS"af12", IPTOS_DSCP_AF12 },
+    { CUS"af13", IPTOS_DSCP_AF13 },
+    { CUS"af21", IPTOS_DSCP_AF21 },
+    { CUS"af22", IPTOS_DSCP_AF22 },
+    { CUS"af23", IPTOS_DSCP_AF23 },
+    { CUS"af31", IPTOS_DSCP_AF31 },
+    { CUS"af32", IPTOS_DSCP_AF32 },
+    { CUS"af33", IPTOS_DSCP_AF33 },
+    { CUS"af41", IPTOS_DSCP_AF41 },
+    { CUS"af42", IPTOS_DSCP_AF42 },
+    { CUS"af43", IPTOS_DSCP_AF43 },
+    { CUS"ef", IPTOS_DSCP_EF },
+#endif
+#ifdef IPTOS_LOWCOST
+    { CUS"lowcost", IPTOS_LOWCOST },
+#endif
+    { CUS"lowdelay", IPTOS_LOWDELAY },
+#ifdef IPTOS_MINCOST
+    { CUS"mincost", IPTOS_MINCOST },
+#endif
+    { CUS"reliability", IPTOS_RELIABILITY },
+    { CUS"throughput", IPTOS_THROUGHPUT }
+};
+static int dscp_table_size =
+  sizeof(dscp_table) / sizeof(struct dscp_name_tableentry);
+
+/* DSCP values change by protocol family, and so do the options used for
+setsockopt(); this utility does all the lookups.  It takes an unexpanded
+option string, expands it, strips off affix whitespace, then checks if it's
+a number.  If all of what's left is a number, then that's how the option will
+be parsed and success/failure is a range check.  If it's not all a number,
+then it must be a supported keyword.
+
+Arguments:
+  dscp_name   a string, so far unvalidated
+  af          address_family in use
+  level       setsockopt level to use
+  optname     setsockopt name to use
+  dscp_value  value for dscp_name
+
+Returns: TRUE if okay to setsockopt(), else FALSE
+
+*level and *optname may be set even if FALSE is returned
+*/
+
+BOOL
+dscp_lookup(const uschar *dscp_name, int af,
+    int *level, int *optname, int *dscp_value)
+{
+uschar *dscp_lookup, *p;
+int first, last;
+long rawlong;
+
+if (af == AF_INET)
+  {
+  *level = IPPROTO_IP;
+  *optname = IP_TOS;
+  }
+#if HAVE_IPV6 && defined(IPV6_TCLASS)
+else if (af == AF_INET6)
+  {
+  *level = IPPROTO_IPV6;
+  *optname = IPV6_TCLASS;
+  }
+#endif
+else
+  {
+  DEBUG(D_transport)
+    debug_printf("Unhandled address family %d in dscp_lookup()\n", af);
+  return FALSE;
+  }
+if (!dscp_name)
+  {
+  DEBUG(D_transport)
+    debug_printf("[empty DSCP]\n");
+  return FALSE;
+  }
+dscp_lookup = expand_string(US dscp_name);
+if (dscp_lookup == NULL || *dscp_lookup == '\0')
+  return FALSE;
+
+p = dscp_lookup + Ustrlen(dscp_lookup) - 1;
+while (isspace(*p)) *p-- = '\0';
+while (isspace(*dscp_lookup) && dscp_lookup < p) dscp_lookup++;
+if (*dscp_lookup == '\0')
+  return FALSE;
+
+rawlong = Ustrtol(dscp_lookup, &p, 0);
+if (p != dscp_lookup && *p == '\0')
+  {
+  /* We have six bits available, which will end up shifted to fit in 0xFC mask.
+  RFC 2597 defines the values unshifted. */
+  if (rawlong < 0 || rawlong > 0x3F)
+    {
+    DEBUG(D_transport)
+      debug_printf("DSCP value %ld out of range, ignored.\n", rawlong);
+    return FALSE;
+    }
+  *dscp_value = rawlong << 2;
+  return TRUE;
+  }
+
+first = 0;
+last = dscp_table_size;
+while (last > first)
+  {
+  int middle = (first + last)/2;
+  int c = Ustrcmp(dscp_lookup, dscp_table[middle].name);
+  if (c == 0)
+    {
+    *dscp_value = dscp_table[middle].value;
+    return TRUE;
+    }
+  else if (c > 0)
+    {
+    first = middle + 1;
+    }
+  else
+    {
+    last = middle;
+    }
+  }
+return FALSE;
+}
+
+void
+dscp_list_to_stream(FILE *stream)
+{
+int i;
+for (i=0; i < dscp_table_size; ++i)
+  fprintf(stream, "%s\n", dscp_table[i].name);
+}
+
+
 /* End of ip.c */