BIO socket connect failure was not handled correctly.
[openssl.git] / crypto / bio / bss_conn.c
index 9b2cee44cf176a418a6367dbce86cc6666eacfbe..c1abe87e250d878879ab2e410a7b1ec69547cd95 100644 (file)
@@ -1,4 +1,3 @@
-/* crypto/bio/bss_conn.c */
 /* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
  * All rights reserved.
  *
 
 #include <stdio.h>
 #include <errno.h>
-#define USE_SOCKETS
-#include "cryptlib.h"
-#include <openssl/bio.h>
 
-#ifndef OPENSSL_NO_SOCK
+#include "bio_lcl.h"
 
-# if (defined(OPENSSL_SYS_VMS) && __VMS_VER < 70000000)
-/* FIONBIO used as a switch to enable ioctl, and that isn't in VMS < 7.0 */
-#  undef FIONBIO
-# endif
+#ifndef OPENSSL_NO_SOCK
 
 typedef struct bio_connect_st {
     int state;
+    int connect_family;
     char *param_hostname;
-    char *param_port;
-    int nbio;
-    unsigned char ip[4];
-    unsigned short port;
-    struct sockaddr_in them;
+    char *param_service;
+    int connect_mode;
+
+    BIO_ADDRINFO *addr_first;
+    const BIO_ADDRINFO *addr_iter;
     /*
      * int socket; this will be kept in bio->num so that it is compatible
      * with the bss_sock bio
@@ -102,7 +96,14 @@ static void conn_close_socket(BIO *data);
 BIO_CONNECT *BIO_CONNECT_new(void);
 void BIO_CONNECT_free(BIO_CONNECT *a);
 
-static BIO_METHOD methods_connectp = {
+#define BIO_CONN_S_BEFORE                1
+#define BIO_CONN_S_GET_ADDR              2
+#define BIO_CONN_S_CREATE_SOCKET         3
+#define BIO_CONN_S_CONNECT               4
+#define BIO_CONN_S_OK                    5
+#define BIO_CONN_S_BLOCKED_CONNECT       6
+
+static const BIO_METHOD methods_connectp = {
     BIO_TYPE_CONNECT,
     "socket connect",
     conn_write,
@@ -118,8 +119,6 @@ static BIO_METHOD methods_connectp = {
 static int conn_state(BIO *b, BIO_CONNECT *c)
 {
     int ret = -1, i;
-    unsigned long l;
-    char *p, *q;
     int (*cb) (const BIO *, int, int) = NULL;
 
     if (c->info_callback != NULL)
@@ -128,122 +127,103 @@ static int conn_state(BIO *b, BIO_CONNECT *c)
     for (;;) {
         switch (c->state) {
         case BIO_CONN_S_BEFORE:
-            p = c->param_hostname;
-            if (p == NULL) {
-                BIOerr(BIO_F_CONN_STATE, BIO_R_NO_HOSTNAME_SPECIFIED);
+            if (c->param_hostname == NULL && c->param_service == NULL) {
+                BIOerr(BIO_F_CONN_STATE, BIO_R_NO_HOSTNAME_OR_SERVICE_SPECIFIED);
+                ERR_add_error_data(4,
+                                   "hostname=", c->param_hostname,
+                                   " service=", c->param_service);
                 goto exit_loop;
             }
-            for (; *p != '\0'; p++) {
-                if ((*p == ':') || (*p == '/'))
-                    break;
-            }
+            c->state = BIO_CONN_S_GET_ADDR;
+            break;
 
-            i = *p;
-            if ((i == ':') || (i == '/')) {
-
-                *(p++) = '\0';
-                if (i == ':') {
-                    for (q = p; *q; q++)
-                        if (*q == '/') {
-                            *q = '\0';
-                            break;
-                        }
-                    OPENSSL_free(c->param_port);
-                    c->param_port = BUF_strdup(p);
+        case BIO_CONN_S_GET_ADDR:
+            {
+                int family = AF_UNSPEC;
+                switch (c->connect_family) {
+                case BIO_FAMILY_IPV6:
+                    if (1) { /* This is a trick we use to avoid bit rot.
+                              * at least the "else" part will always be
+                              * compiled.
+                              */
+#ifdef AF_INET6
+                        family = AF_INET6;
+                    } else {
+#endif
+                        BIOerr(BIO_F_CONN_STATE, BIO_R_UNAVAILABLE_IP_FAMILY);
+                        goto exit_loop;
+                    }
+                    break;
+                case BIO_FAMILY_IPV4:
+                    family = AF_INET;
+                    break;
+                case BIO_FAMILY_IPANY:
+                    family = AF_UNSPEC;
+                    break;
+                default:
+                    BIOerr(BIO_F_CONN_STATE, BIO_R_UNSUPPORTED_IP_FAMILY);
+                    goto exit_loop;
                 }
+                if (BIO_lookup(c->param_hostname, c->param_service,
+                               BIO_LOOKUP_CLIENT,
+                               family, SOCK_STREAM, &c->addr_first) == 0)
+                    goto exit_loop;
             }
-
-            if (c->param_port == NULL) {
-                BIOerr(BIO_F_CONN_STATE, BIO_R_NO_PORT_SPECIFIED);
-                ERR_add_error_data(2, "host=", c->param_hostname);
+            if (c->addr_first == NULL) {
+                BIOerr(BIO_F_CONN_STATE, BIO_R_LOOKUP_RETURNED_NOTHING);
                 goto exit_loop;
             }
-            c->state = BIO_CONN_S_GET_IP;
-            break;
-
-        case BIO_CONN_S_GET_IP:
-            if (BIO_get_host_ip(c->param_hostname, &(c->ip[0])) <= 0)
-                goto exit_loop;
-            c->state = BIO_CONN_S_GET_PORT;
-            break;
-
-        case BIO_CONN_S_GET_PORT:
-            if (c->param_port == NULL) {
-                /* abort(); */
-                goto exit_loop;
-            } else if (BIO_get_port(c->param_port, &c->port) <= 0)
-                goto exit_loop;
+            c->addr_iter = c->addr_first;
             c->state = BIO_CONN_S_CREATE_SOCKET;
             break;
 
         case BIO_CONN_S_CREATE_SOCKET:
-            /* now setup address */
-            memset(&c->them, 0, sizeof(c->them));
-            c->them.sin_family = AF_INET;
-            c->them.sin_port = htons((unsigned short)c->port);
-            l = (unsigned long)
-                ((unsigned long)c->ip[0] << 24L) |
-                ((unsigned long)c->ip[1] << 16L) |
-                ((unsigned long)c->ip[2] << 8L) | ((unsigned long)c->ip[3]);
-            c->them.sin_addr.s_addr = htonl(l);
-            c->state = BIO_CONN_S_CREATE_SOCKET;
-
-            ret = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
-            if (ret == INVALID_SOCKET) {
+            ret = BIO_socket(BIO_ADDRINFO_family(c->addr_iter),
+                             BIO_ADDRINFO_socktype(c->addr_iter),
+                             BIO_ADDRINFO_protocol(c->addr_iter), 0);
+            if (ret == (int)INVALID_SOCKET) {
                 SYSerr(SYS_F_SOCKET, get_last_socket_error());
-                ERR_add_error_data(4, "host=", c->param_hostname,
-                                   ":", c->param_port);
+                ERR_add_error_data(4,
+                                   "hostname=", c->param_hostname,
+                                   " service=", c->param_service);
                 BIOerr(BIO_F_CONN_STATE, BIO_R_UNABLE_TO_CREATE_SOCKET);
                 goto exit_loop;
             }
             b->num = ret;
-            c->state = BIO_CONN_S_NBIO;
-            break;
-
-        case BIO_CONN_S_NBIO:
-            if (c->nbio) {
-                if (!BIO_socket_nbio(b->num, 1)) {
-                    BIOerr(BIO_F_CONN_STATE, BIO_R_ERROR_SETTING_NBIO);
-                    ERR_add_error_data(4, "host=",
-                                       c->param_hostname, ":", c->param_port);
-                    goto exit_loop;
-                }
-            }
             c->state = BIO_CONN_S_CONNECT;
-
-# if defined(SO_KEEPALIVE)
-            i = 1;
-            i = setsockopt(b->num, SOL_SOCKET, SO_KEEPALIVE, (char *)&i,
-                           sizeof(i));
-            if (i < 0) {
-                SYSerr(SYS_F_SOCKET, get_last_socket_error());
-                ERR_add_error_data(4, "host=", c->param_hostname,
-                                   ":", c->param_port);
-                BIOerr(BIO_F_CONN_STATE, BIO_R_KEEPALIVE);
-                goto exit_loop;
-            }
-# endif
             break;
 
         case BIO_CONN_S_CONNECT:
             BIO_clear_retry_flags(b);
-            ret = connect(b->num,
-                          (struct sockaddr *)&c->them, sizeof(c->them));
+            ret = BIO_connect(b->num, BIO_ADDRINFO_address(c->addr_iter),
+                              BIO_SOCK_KEEPALIVE | c->connect_mode);
             b->retry_reason = 0;
-            if (ret < 0) {
+            if (ret == 0) {
                 if (BIO_sock_should_retry(ret)) {
                     BIO_set_retry_special(b);
                     c->state = BIO_CONN_S_BLOCKED_CONNECT;
                     b->retry_reason = BIO_RR_CONNECT;
+                    ERR_clear_error();
+                } else if ((c->addr_iter = BIO_ADDRINFO_next(c->addr_iter))
+                           != NULL) {
+                    /*
+                     * if there are more addresses to try, do that first
+                     */
+                    BIO_closesocket(b->num);
+                    c->state = BIO_CONN_S_CREATE_SOCKET;
+                    ERR_clear_error();
+                    break;
                 } else {
                     SYSerr(SYS_F_CONNECT, get_last_socket_error());
-                    ERR_add_error_data(4, "host=",
-                                       c->param_hostname, ":", c->param_port);
+                    ERR_add_error_data(4,
+                                       "hostname=", c->param_hostname,
+                                       " service=", c->param_service);
                     BIOerr(BIO_F_CONN_STATE, BIO_R_CONNECT_ERROR);
                 }
                 goto exit_loop;
-            } else
+            } else {
                 c->state = BIO_CONN_S_OK;
+            }
             break;
 
         case BIO_CONN_S_BLOCKED_CONNECT:
@@ -251,8 +231,9 @@ static int conn_state(BIO *b, BIO_CONNECT *c)
             if (i) {
                 BIO_clear_retry_flags(b);
                 SYSerr(SYS_F_CONNECT, i);
-                ERR_add_error_data(4, "host=",
-                                   c->param_hostname, ":", c->param_port);
+                ERR_add_error_data(4,
+                                   "hostname=", c->param_hostname,
+                                   " service=", c->param_service);
                 BIOerr(BIO_F_CONN_STATE, BIO_R_NBIO_CONNECT_ERROR);
                 ret = 0;
                 goto exit_loop;
@@ -286,19 +267,10 @@ BIO_CONNECT *BIO_CONNECT_new(void)
 {
     BIO_CONNECT *ret;
 
-    if ((ret = OPENSSL_malloc(sizeof(*ret))) == NULL)
+    if ((ret = OPENSSL_zalloc(sizeof(*ret))) == NULL)
         return (NULL);
     ret->state = BIO_CONN_S_BEFORE;
-    ret->param_hostname = NULL;
-    ret->param_port = NULL;
-    ret->info_callback = NULL;
-    ret->nbio = 0;
-    ret->ip[0] = 0;
-    ret->ip[1] = 0;
-    ret->ip[2] = 0;
-    ret->ip[3] = 0;
-    ret->port = 0;
-    memset(&ret->them, 0, sizeof(ret->them));
+    ret->connect_family = BIO_FAMILY_IPANY;
     return (ret);
 }
 
@@ -308,11 +280,12 @@ void BIO_CONNECT_free(BIO_CONNECT *a)
         return;
 
     OPENSSL_free(a->param_hostname);
-    OPENSSL_free(a->param_port);
+    OPENSSL_free(a->param_service);
+    BIO_ADDRINFO_free(a->addr_first);
     OPENSSL_free(a);
 }
 
-BIO_METHOD *BIO_s_connect(void)
+const BIO_METHOD *BIO_s_connect(void)
 {
     return (&methods_connectp);
 }
@@ -320,7 +293,7 @@ BIO_METHOD *BIO_s_connect(void)
 static int conn_new(BIO *bi)
 {
     bi->init = 0;
-    bi->num = INVALID_SOCKET;
+    bi->num = (int)INVALID_SOCKET;
     bi->flags = 0;
     if ((bi->ptr = (char *)BIO_CONNECT_new()) == NULL)
         return (0);
@@ -333,12 +306,12 @@ static void conn_close_socket(BIO *bio)
     BIO_CONNECT *c;
 
     c = (BIO_CONNECT *)bio->ptr;
-    if (bio->num != INVALID_SOCKET) {
+    if (bio->num != (int)INVALID_SOCKET) {
         /* Only do a shutdown if things were established */
         if (c->state == BIO_CONN_S_OK)
             shutdown(bio->num, 2);
-        closesocket(bio->num);
-        bio->num = INVALID_SOCKET;
+        BIO_closesocket(bio->num);
+        bio->num = (int)INVALID_SOCKET;
     }
 }
 
@@ -410,7 +383,7 @@ static long conn_ctrl(BIO *b, int cmd, long num, void *ptr)
 {
     BIO *dbio;
     int *ip;
-    const char **pptr;
+    const char **pptr = NULL;
     long ret = 1;
     BIO_CONNECT *data;
 
@@ -421,6 +394,8 @@ static long conn_ctrl(BIO *b, int cmd, long num, void *ptr)
         ret = 0;
         data->state = BIO_CONN_S_BEFORE;
         conn_close_socket(b);
+        BIO_ADDRINFO_free(data->addr_first);
+        data->addr_first = NULL;
         b->flags = 0;
         break;
     case BIO_C_DO_STATE_MACHINE:
@@ -435,49 +410,78 @@ static long conn_ctrl(BIO *b, int cmd, long num, void *ptr)
             pptr = (const char **)ptr;
             if (num == 0) {
                 *pptr = data->param_hostname;
-
             } else if (num == 1) {
-                *pptr = data->param_port;
+                *pptr = data->param_service;
             } else if (num == 2) {
-                *pptr = (char *)&(data->ip[0]);
+                *pptr = (const char *)BIO_ADDRINFO_address(data->addr_iter);
             } else if (num == 3) {
-                *((int *)ptr) = data->port;
+                switch (BIO_ADDRINFO_family(data->addr_iter)) {
+# ifdef AF_INET6
+                case AF_INET6:
+                    ret = BIO_FAMILY_IPV6;
+                    break;
+# endif
+                case AF_INET:
+                    ret = BIO_FAMILY_IPV4;
+                    break;
+                case 0:
+                    ret = data->connect_family;
+                    break;
+                default:
+                    ret = -1;
+                    break;
+                }
+            } else {
+                ret = 0;
             }
-            if ((!b->init) || (ptr == NULL))
-                *pptr = "not initialized";
-            ret = 1;
+        } else {
+            ret = 0;
         }
         break;
     case BIO_C_SET_CONNECT:
         if (ptr != NULL) {
             b->init = 1;
             if (num == 0) {
+                char *hold_service = data->param_service;
+                /* We affect the hostname regardless.  However, the input
+                 * string might contain a host:service spec, so we must
+                 * parse it, which might or might not affect the service
+                 */
                 OPENSSL_free(data->param_hostname);
-                data->param_hostname = BUF_strdup(ptr);
+                data->param_hostname = NULL;
+                ret = BIO_parse_hostserv(ptr,
+                                         &data->param_hostname,
+                                         &data->param_service,
+                                         BIO_PARSE_PRIO_HOST);
+                if (hold_service != data->param_service)
+                    OPENSSL_free(hold_service);
             } else if (num == 1) {
-                OPENSSL_free(data->param_port);
-                data->param_port = BUF_strdup(ptr);
+                OPENSSL_free(data->param_service);
+                data->param_service = BUF_strdup(ptr);
             } else if (num == 2) {
-                char buf[16];
-                unsigned char *p = ptr;
-
-                BIO_snprintf(buf, sizeof buf, "%d.%d.%d.%d",
-                             p[0], p[1], p[2], p[3]);
-                OPENSSL_free(data->param_hostname);
-                data->param_hostname = BUF_strdup(buf);
-                memcpy(&(data->ip[0]), ptr, 4);
+                const BIO_ADDR *addr = (const BIO_ADDR *)ptr;
+                if (ret) {
+                    data->param_hostname = BIO_ADDR_hostname_string(addr, 1);
+                    data->param_service = BIO_ADDR_service_string(addr, 1);
+                    BIO_ADDRINFO_free(data->addr_first);
+                    data->addr_first = NULL;
+                    data->addr_iter = NULL;
+                }
             } else if (num == 3) {
-                char buf[DECIMAL_SIZE(int) + 1];
-
-                BIO_snprintf(buf, sizeof buf, "%d", *(int *)ptr);
-                OPENSSL_free(data->param_port);
-                data->param_port = BUF_strdup(buf);
-                data->port = *(int *)ptr;
+                data->connect_family = *(int *)ptr;
+            } else {
+                ret = 0;
             }
         }
         break;
     case BIO_C_SET_NBIO:
-        data->nbio = (int)num;
+        if (num != 0)
+            data->connect_mode |= BIO_SOCK_NONBLOCK;
+        else
+            data->connect_mode &= ~BIO_SOCK_NONBLOCK;
+        break;
+    case BIO_C_SET_CONNECT_MODE:
+        data->connect_mode = (int)num;
         break;
     case BIO_C_GET_FD:
         if (b->init) {
@@ -503,11 +507,12 @@ static long conn_ctrl(BIO *b, int cmd, long num, void *ptr)
     case BIO_CTRL_DUP:
         {
             dbio = (BIO *)ptr;
-            if (data->param_port)
-                BIO_set_conn_port(dbio, data->param_port);
             if (data->param_hostname)
                 BIO_set_conn_hostname(dbio, data->param_hostname);
-            BIO_set_nbio(dbio, data->nbio);
+            if (data->param_service)
+                BIO_set_conn_port(dbio, data->param_service);
+            BIO_set_conn_ip_family(dbio, data->connect_family);
+            BIO_set_conn_mode(dbio, data->connect_mode);
             /*
              * FIXME: the cast of the function seems unlikely to be a good
              * idea