axtls: now done non-blocking
authorEric Hu <EHu@directv.com>
Wed, 12 Jun 2013 08:36:31 +0000 (10:36 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Wed, 12 Jun 2013 08:37:32 +0000 (10:37 +0200)
lib/axtls.c
lib/axtls.h
lib/urldata.h

index 2180640..44e6b93 100644 (file)
 #define _MPRINTF_REPLACE /* use our functions only */
 #include <curl/mprintf.h>
 #include "curl_memory.h"
+#include <unistd.h>
 /* The last #include file should be: */
 #include "memdebug.h"
 #include "hostcheck.h"
 
 
-/* SSL_read is opied from axTLS compat layer */
-static int SSL_read(SSL *ssl, void *buf, int num)
-{
-  uint8_t *read_buf;
-  int ret;
-
-  while((ret = ssl_read(ssl, &read_buf)) == SSL_OK);
-
-  if(ret > SSL_OK) {
-    memcpy(buf, read_buf, ret > num ? num : ret);
-  }
-
-  return ret;
-}
-
 /* Global axTLS init, called from Curl_ssl_init() */
 int Curl_axtls_init(void)
 {
@@ -131,24 +117,24 @@ static CURLcode map_error_to_curl(int axtls_err)
 static Curl_recv axtls_recv;
 static Curl_send axtls_send;
 
-static void free_ssl_structs(SSL_CTX *ssl_ctx, SSL *ssl)
+static void free_ssl_structs(struct ssl_connect_data *connssl)
 {
-  if(ssl) {
-    ssl_free (ssl);
+  if(connssl->ssl) {
+    ssl_free (connssl->ssl);
+    connssl->ssl = NULL;
   }
-  if(ssl_ctx) {
-    ssl_ctx_free(ssl_ctx);
+  if(connssl->ssl_ctx) {
+    ssl_ctx_free(connssl->ssl_ctx);
+    connssl->ssl_ctx = NULL;
   }
 }
 
 /*
- * This function is called after the TCP connect has completed. Setup the TLS
- * layer and do all necessary magic.
+ * For both blocking and non-blocking connects, this function sets up the
+ * ssl context and state.  This function is called after the TCP connect
+ * has completed.
  */
-CURLcode
-Curl_axtls_connect(struct connectdata *conn,
-                  int sockindex)
-
+static CURLcode connect_prep(struct connectdata *conn, int sockindex)
 {
   struct SessionHandle *data = conn->data;
   SSL_CTX *ssl_ctx;
@@ -158,14 +144,13 @@ Curl_axtls_connect(struct connectdata *conn,
   int i, ssl_fcn_return;
   const uint8_t *ssl_sessionid;
   size_t ssl_idsize;
-  const char *peer_CN;
-  uint32_t dns_altname_index;
-  const char *dns_altname;
-  int8_t found_subject_alt_names = 0;
-  int8_t found_subject_alt_name_matching_conn = 0;
 
-  /* Assuming users will not compile in custom key/cert to axTLS */
-  uint32_t client_option = SSL_NO_DEFAULT_KEY|SSL_SERVER_VERIFY_LATER;
+  /* Assuming users will not compile in custom key/cert to axTLS.
+  *  Also, even for blocking connects, use axTLS non-blocking feature.
+  */
+  uint32_t client_option = SSL_NO_DEFAULT_KEY |
+    SSL_SERVER_VERIFY_LATER |
+    SSL_CONNECT_IN_PARTS;
 
   if(conn->ssl[sockindex].state == ssl_connection_complete)
     /* to make us tolerant against being called more than once for the
@@ -194,6 +179,9 @@ Curl_axtls_connect(struct connectdata *conn,
     return CURLE_SSL_CONNECT_ERROR;
   }
 
+  conn->ssl[sockindex].ssl_ctx = ssl_ctx;
+  conn->ssl[sockindex].ssl = NULL;
+
   /* Load the trusted CA cert bundle file */
   if(data->set.ssl.CAfile) {
     if(ssl_obj_load(ssl_ctx, SSL_OBJ_X509_CACERT, data->set.ssl.CAfile, NULL)
@@ -201,8 +189,6 @@ Curl_axtls_connect(struct connectdata *conn,
       infof(data, "error reading ca cert file %s \n",
             data->set.ssl.CAfile);
       if(data->set.ssl.verifypeer) {
-        Curl_axtls_close(conn, sockindex);
-        free_ssl_structs(ssl_ctx, ssl);
         return CURLE_SSL_CACERT_BADFILE;
       }
     }
@@ -236,8 +222,6 @@ Curl_axtls_connect(struct connectdata *conn,
     if(cert_types[i] == 0) {
       failf(data, "%s is not x509 or pkcs12 format",
             data->set.str[STRING_CERT]);
-      Curl_axtls_close(conn, sockindex);
-      free_ssl_structs(ssl_ctx, ssl);
       return CURLE_SSL_CERTPROBLEM;
     }
   }
@@ -262,8 +246,6 @@ Curl_axtls_connect(struct connectdata *conn,
     if(key_types[i] == 0) {
       failf(data, "Failure: %s is not a supported key file",
             data->set.str[STRING_KEY]);
-      Curl_axtls_close(conn, sockindex);
-      free_ssl_structs(ssl_ctx, ssl);
       return CURLE_SSL_CONNECT_ERROR;
     }
   }
@@ -284,15 +266,25 @@ Curl_axtls_connect(struct connectdata *conn,
   else
     ssl = ssl_client_new(ssl_ctx, conn->sock[sockindex], NULL, 0);
 
-  /* Check to make sure handshake was ok. */
-  ssl_fcn_return = ssl_handshake_status(ssl);
-  if(ssl_fcn_return != SSL_OK) {
-    Curl_axtls_close(conn, sockindex);
-    free_ssl_structs(ssl_ctx, ssl);
-    ssl_display_error(ssl_fcn_return); /* goes to stdout. */
-    return map_error_to_curl(ssl_fcn_return);
-  }
-  infof (data, "handshake completed successfully\n");
+  conn->ssl[sockindex].ssl = ssl;
+  return CURLE_OK;
+}
+
+/*
+ * For both blocking and non-blocking connects, this function finalizes the
+ * SSL connection.
+ */
+static CURLcode connect_finish(struct connectdata *conn, int sockindex)
+{
+  struct SessionHandle *data = conn->data;
+  SSL *ssl = conn->ssl[sockindex].ssl;
+  const uint8_t *ssl_sessionid;
+  size_t ssl_idsize;
+  const char *peer_CN;
+  uint32_t dns_altname_index;
+  const char *dns_altname;
+  int8_t found_subject_alt_names = 0;
+  int8_t found_subject_alt_name_matching_conn = 0;
 
   /* Here, gtls.c gets the peer certificates and fails out depending on
    * settings in "data."  axTLS api doesn't have get cert chain fcn, so omit?
@@ -302,9 +294,8 @@ Curl_axtls_connect(struct connectdata *conn,
   if(data->set.ssl.verifypeer) {
     if(ssl_verify_cert(ssl) != SSL_OK) {
       Curl_axtls_close(conn, sockindex);
-      free_ssl_structs(ssl_ctx, ssl);
       failf(data, "server cert verify failed");
-      return CURLE_SSL_CONNECT_ERROR;
+      return CURLE_PEER_FAILED_VERIFICATION;
     }
   }
   else
@@ -321,7 +312,6 @@ Curl_axtls_connect(struct connectdata *conn,
    *    this, but a couple fields are available.
    */
 
-
   /* There is no (DNS) Altnames count in the version 1.4.8 API. There is a
      risk of an inifite loop */
   for(dns_altname_index = 0; ; dns_altname_index++) {
@@ -344,13 +334,13 @@ Curl_axtls_connect(struct connectdata *conn,
     if(data->set.ssl.verifyhost) {
       /* Break connection ! */
       Curl_axtls_close(conn, sockindex);
-      free_ssl_structs(ssl_ctx, ssl);
       failf(data, "\tsubjectAltName(s) do not match %s\n",
             conn->host.dispname);
       return CURLE_PEER_FAILED_VERIFICATION;
     }
     else
       infof(data, "\tsubjectAltName(s) do not match %s\n",
+            conn->host.dispname);
   }
   else if(found_subject_alt_names == 0) {
     /* Per RFC2818, when no Subject Alt Names were available, examine the peer
@@ -359,7 +349,6 @@ Curl_axtls_connect(struct connectdata *conn,
     if(peer_CN == NULL) {
       if(data->set.ssl.verifyhost) {
         Curl_axtls_close(conn, sockindex);
-        free_ssl_structs(ssl_ctx, ssl);
         failf(data, "unable to obtain common name from peer certificate");
         return CURLE_PEER_FAILED_VERIFICATION;
       }
@@ -371,7 +360,6 @@ Curl_axtls_connect(struct connectdata *conn,
         if(data->set.ssl.verifyhost) {
           /* Break connection ! */
           Curl_axtls_close(conn, sockindex);
-          free_ssl_structs(ssl_ctx, ssl);
           failf(data, "\tcommon name \"%s\" does not match \"%s\"\n",
                 peer_CN, conn->host.dispname);
           return CURLE_PEER_FAILED_VERIFICATION;
@@ -385,8 +373,6 @@ Curl_axtls_connect(struct connectdata *conn,
 
   /* General housekeeping */
   conn->ssl[sockindex].state = ssl_connection_complete;
-  conn->ssl[sockindex].ssl = ssl;
-  conn->ssl[sockindex].ssl_ctx = ssl_ctx;
   conn->recv[sockindex] = axtls_recv;
   conn->send[sockindex] = axtls_send;
 
@@ -400,6 +386,107 @@ Curl_axtls_connect(struct connectdata *conn,
   return CURLE_OK;
 }
 
+/*
+ * Use axTLS's non-blocking connection feature to open an SSL connection.
+ * This is called after a TCP connection is already established.
+*/
+CURLcode Curl_axtls_connect_nonblocking(
+    struct connectdata *conn,
+    int sockindex,
+    bool *done)
+{
+  CURLcode conn_step;
+  int ssl_fcn_return;
+
+ *done = FALSE;
+  /* connectdata is calloc'd and connecting_state is only changed in this
+     function, so this is safe, as the state is effectively initialized. */
+  if(conn->ssl[sockindex].connecting_state == ssl_connect_1) {
+    conn_step = connect_prep(conn, sockindex);
+    if(conn_step != CURLE_OK) {
+      Curl_axtls_close(conn, sockindex);
+      return conn_step;
+    }
+    conn->ssl[sockindex].connecting_state = ssl_connect_2;
+  }
+
+  if(conn->ssl[sockindex].connecting_state == ssl_connect_2) {
+    /* Check to make sure handshake was ok. */
+    if(ssl_handshake_status(conn->ssl[sockindex].ssl) != SSL_OK) {
+      ssl_fcn_return = ssl_read(conn->ssl[sockindex].ssl, NULL);
+      if(ssl_fcn_return < 0) {
+        Curl_axtls_close(conn, sockindex);
+        ssl_display_error(ssl_fcn_return); /* goes to stdout. */
+        return map_error_to_curl(ssl_fcn_return);
+      }
+      else {
+        return CURLE_OK; /* Return control to caller for retries */
+      }
+    }
+    infof (conn->data, "handshake completed successfully\n");
+    conn->ssl[sockindex].connecting_state = ssl_connect_3;
+  }
+
+  if(conn->ssl[sockindex].connecting_state == ssl_connect_3) {
+    conn_step = connect_finish(conn, sockindex);
+    if(conn_step != CURLE_OK) {
+      Curl_axtls_close(conn, sockindex);
+      return conn_step;
+    }
+
+    /* Reset connect state */
+    conn->ssl[sockindex].connecting_state = ssl_connect_1;
+
+    *done = TRUE;
+    return CURLE_OK;
+  }
+
+  /* Unrecognized state.  Things are very bad. */
+  conn->ssl[sockindex].state  = ssl_connection_none;
+  conn->ssl[sockindex].connecting_state = ssl_connect_1;
+  /* Return value perhaps not strictly correct, but distinguishes the issue.*/
+  return CURLE_BAD_FUNCTION_ARGUMENT;
+}
+
+
+/*
+ * This function is called after the TCP connect has completed. Setup the TLS
+ * layer and do all necessary magic for a blocking connect.
+ */
+CURLcode
+Curl_axtls_connect(struct connectdata *conn,
+                  int sockindex)
+
+{
+  CURLcode conn_step = connect_prep(conn, sockindex);
+  int ssl_fcn_return;
+  SSL *ssl = conn->ssl[sockindex].ssl;
+
+  if(conn_step != CURLE_OK) {
+    Curl_axtls_close(conn, sockindex);
+    return conn_step;
+  }
+
+  /* Check to make sure handshake was ok. */
+  while(ssl_handshake_status(ssl) != SSL_OK) {
+    ssl_fcn_return = ssl_read(ssl, NULL);
+    if(ssl_fcn_return < 0) {
+      Curl_axtls_close(conn, sockindex);
+      ssl_display_error(ssl_fcn_return); /* goes to stdout. */
+      return map_error_to_curl(ssl_fcn_return);
+    }
+    usleep(10000);
+  }
+  infof (conn->data, "handshake completed successfully\n");
+
+  conn_step = connect_finish(conn, sockindex);
+  if(conn_step != CURLE_OK) {
+    Curl_axtls_close(conn, sockindex);
+    return conn_step;
+  }
+
+  return CURLE_OK;
+}
 
 /* return number of sent (non-SSL) bytes */
 static ssize_t axtls_send(struct connectdata *conn,
@@ -433,7 +520,7 @@ void Curl_axtls_close(struct connectdata *conn, int sockindex)
   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
 
   infof(conn->data, "  Curl_axtls_close\n");
-  if(connssl->ssl) {
+
     /* line from ssluse.c: (void)SSL_shutdown(connssl->ssl);
        axTLS compat layer does nothing for SSL_shutdown */
 
@@ -441,13 +528,7 @@ void Curl_axtls_close(struct connectdata *conn, int sockindex)
        equivalent.  ssl_free and ssl_ctx_free close things.
        SSL_set_connect_state(connssl->handle); */
 
-    ssl_free (connssl->ssl);
-    connssl->ssl = NULL;
-  }
-  if(connssl->ssl_ctx) {
-    ssl_ctx_free (connssl->ssl_ctx);
-    connssl->ssl_ctx = NULL;
-  }
+  free_ssl_structs(connssl);
 }
 
 /*
@@ -462,8 +543,7 @@ int Curl_axtls_shutdown(struct connectdata *conn, int sockindex)
   int retval = 0;
   struct ssl_connect_data *connssl = &conn->ssl[sockindex];
   struct SessionHandle *data = conn->data;
-  char buf[120]; /* We will use this for the OpenSSL error buffer, so it has
-                    to be at least 120 bytes long. */
+  uint8_t *buf;
   ssize_t nread;
 
   infof(conn->data, "  Curl_axtls_shutdown\n");
@@ -483,9 +563,10 @@ int Curl_axtls_shutdown(struct connectdata *conn, int sockindex)
                                  CURL_SOCKET_BAD, SSL_SHUTDOWN_TIMEOUT);
     if(what > 0) {
       /* Something to read, let's do it and hope that it is the close
-         notify alert from the server */
-      nread = (ssize_t)SSL_read(conn->ssl[sockindex].ssl, buf,
-                                sizeof(buf));
+         notify alert from the server.  buf is managed internally by
+         axTLS and will be released upon calling ssl_free via
+         free_ssl_structs. */
+      nread = (ssize_t)ssl_read(connssl->ssl, &buf);
 
       if(nread < SSL_OK) {
         failf(data, "close notify alert not received during shutdown");
@@ -502,8 +583,7 @@ int Curl_axtls_shutdown(struct connectdata *conn, int sockindex)
       retval = -1;
     }
 
-    ssl_free (connssl->ssl);
-    connssl->ssl = NULL;
+    free_ssl_structs(connssl);
   }
   return retval;
 }
@@ -516,26 +596,36 @@ static ssize_t axtls_recv(struct connectdata *conn, /* connection data */
 {
   struct ssl_connect_data *connssl = &conn->ssl[num];
   ssize_t ret = 0;
+  uint8_t *read_buf;
 
   infof(conn->data, "  axtls_recv\n");
 
+  *err = CURLE_OK;
   if(connssl) {
-    ret = (ssize_t)SSL_read(conn->ssl[num].ssl, buf, (int)buffersize);
-
-    /* axTLS isn't terribly generous about error reporting */
-    /* With patched axTLS, SSL_CLOSE_NOTIFY=-3.  Hard-coding until axTLS
-       team approves proposed fix. */
-    if(ret == -3 ) {
+    ret = ssl_read(connssl->ssl, &read_buf);
+    if(ret > SSL_OK) {
+      /* ssl_read returns SSL_OK if there is more data to read, so if it is
+         larger, then all data has been read already.  */
+      memcpy(buf, read_buf,
+             (size_t)ret > buffersize ? buffersize : (size_t)ret);
+    }
+    else if(ret == SSL_OK) {
+      /* more data to be read, signal caller to call again */
+      *err = CURLE_AGAIN;
+      ret = -1;
+    }
+    else if(ret == -3) {
+      /* With patched axTLS, SSL_CLOSE_NOTIFY=-3.  Hard-coding until axTLS
+         team approves proposed fix. */
       Curl_axtls_close(conn, num);
     }
-    else if(ret < 0) {
-      failf(conn->data, "axTLS recv error (%d)", (int)ret);
+    else {
+      failf(conn->data, "axTLS recv error (%d)", ret);
       *err = map_error_to_curl(ret);
-      return -1;
+      ret = -1;
     }
   }
 
-  *err = CURLE_OK;
   return ret;
 }
 
index 8d33b1c..db91365 100644 (file)
 int Curl_axtls_init(void);
 int Curl_axtls_cleanup(void);
 CURLcode Curl_axtls_connect(struct connectdata *conn, int sockindex);
+CURLcode Curl_axtls_connect_nonblocking(
+    struct connectdata *conn,
+    int sockindex,
+    bool *done);
 
 /* tell axTLS to close down all open information regarding connections (and
    thus session ID caching etc) */
@@ -47,6 +51,7 @@ int Curl_axtls_check_cxn(struct connectdata *conn);
 #define curlssl_init Curl_axtls_init
 #define curlssl_cleanup Curl_axtls_cleanup
 #define curlssl_connect Curl_axtls_connect
+#define curlssl_connect_nonblocking Curl_axtls_connect_nonblocking
 #define curlssl_session_free(x)  Curl_axtls_session_free(x)
 #define curlssl_close_all Curl_axtls_close_all
 #define curlssl_close Curl_axtls_close
index 8d6c420..0c07cab 100644 (file)
@@ -325,6 +325,7 @@ struct ssl_connect_data {
 #ifdef USE_AXTLS
   SSL_CTX* ssl_ctx;
   SSL*     ssl;
+  ssl_connect_state connecting_state;
 #endif /* USE_AXTLS */
 #ifdef USE_SCHANNEL
   struct curl_schannel_cred *cred;