text: /***************************************************************************
   *                                  _   _ ____  _
   *  Project                     ___| | | |  _ \| |
   *                             / __| | | | |_) | |
   *                            | (__| |_| |  _ <| |___
   *                             \___|\___/|_| \_\_____|
   *
   * Copyright (C) 1998 - 2011, Daniel Stenberg, <daniel@haxx.se>, et al.
   *
   * This software is licensed as described in the file COPYING, which
   * you should have received as part of this distribution. The terms
   * are also available at http://curl.haxx.se/docs/copyright.html.
   *
   * You may opt to use, copy, modify, merge, publish, distribute and/or sell
   * copies of the Software, and permit persons to whom the Software is
   * furnished to do so, under the terms of the COPYING file.
   *
   * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
   * KIND, either express or implied.
   *
   ***************************************************************************/
  
  /*
   * Source file for all CyaSSL-specific code for the TLS/SSL layer. No code
   * but sslgen.c should ever call or use these functions.
   *
   */
  
  #include "setup.h"
  
  #ifdef USE_CYASSL
  
  #ifdef HAVE_LIMITS_H
  #include <limits.h>
  #endif
  
  #ifdef HAVE_SYS_SOCKET_H
  #include <sys/socket.h>
  #endif
  
  #include "urldata.h"
  #include "sendf.h"
  #include "inet_pton.h"
  #include "cyassl.h"
  #include "sslgen.h"
  #include "parsedate.h"
  #include "connect.h" /* for the connect timeout */
  #include "select.h"
  #include "rawstr.h"
  
  #define _MPRINTF_REPLACE /* use our functions only */
  #include <curl/mprintf.h>
  #include "curl_memory.h"
  /* The last #include file should be: */
  #include "memdebug.h"
  
  
  static Curl_recv cyassl_recv;
  static Curl_send cyassl_send;
  
  
  static int do_file_type(const char *type)
  {
    if(!type || !type[0])
      return SSL_FILETYPE_PEM;
    if(Curl_raw_equal(type, "PEM"))
      return SSL_FILETYPE_PEM;
    if(Curl_raw_equal(type, "DER"))
      return SSL_FILETYPE_ASN1;
    return -1;
  }
  
  /*
   * This function loads all the client/CA certificates and CRLs. Setup the TLS
   * layer and do all necessary magic.
   */
  static CURLcode
  cyassl_connect_step1(struct connectdata *conn,
                       int sockindex)
  {
    struct SessionHandle *data = conn->data;
    struct ssl_connect_data* conssl = &conn->ssl[sockindex];
    SSL_METHOD* req_method = NULL;
    void* ssl_sessionid = NULL;
    curl_socket_t sockfd = conn->sock[sockindex];
  
    if(conssl->state == ssl_connection_complete)
      return CURLE_OK;
  
    /* CyaSSL doesn't support SSLv2 */
    if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) {
      failf(data, "CyaSSL does not support SSLv2");
      return CURLE_SSL_CONNECT_ERROR;
    }
  
    /* check to see if we've been told to use an explicit SSL/TLS version */
    switch(data->set.ssl.version) {
    case CURL_SSLVERSION_DEFAULT:
      /* we try to figure out version */
      req_method = SSLv23_client_method();
      break;
    case CURL_SSLVERSION_TLSv1:
      req_method = TLSv1_client_method();
      break;
    case CURL_SSLVERSION_SSLv3:
      req_method = SSLv3_client_method();
      break;
    default:
      req_method = TLSv1_client_method();
    }
  
    if(!req_method) {
      failf(data, "SSL: couldn't create a method!");
      return CURLE_OUT_OF_MEMORY;
    }
  
    if(conssl->ctx)
      SSL_CTX_free(conssl->ctx);
    conssl->ctx = SSL_CTX_new(req_method);
  
    if(!conssl->ctx) {
      failf(data, "SSL: couldn't create a context!");
      return CURLE_OUT_OF_MEMORY;
    }
  
  #ifndef NO_FILESYSTEM
    /* load trusted cacert */
    if(data->set.str[STRING_SSL_CAFILE]) {
      if(!SSL_CTX_load_verify_locations(conssl->ctx,
                                        data->set.str[STRING_SSL_CAFILE],
                                        data->set.str[STRING_SSL_CAPATH])) {
        if(data->set.ssl.verifypeer) {
          /* Fail if we insiste on successfully verifying the server. */
          failf(data,"error setting certificate verify locations:\n"
                "  CAfile: %s\n  CApath: %s\n",
                data->set.str[STRING_SSL_CAFILE]?
                data->set.str[STRING_SSL_CAFILE]: "none",
                data->set.str[STRING_SSL_CAPATH]?
                data->set.str[STRING_SSL_CAPATH] : "none");
          return CURLE_SSL_CACERT_BADFILE;
        }
        else {
          /* Just continue with a warning if no strict  certificate
             verification is required. */
          infof(data, "error setting certificate verify locations,"
                " continuing anyway:\n");
        }
      }
      else {
        /* Everything is fine. */
        infof(data, "successfully set certificate verify locations:\n");
      }
      infof(data,
            "  CAfile: %s\n"
            "  CApath: %s\n",
            data->set.str[STRING_SSL_CAFILE] ? data->set.str[STRING_SSL_CAFILE]:
            "none",
            data->set.str[STRING_SSL_CAPATH] ? data->set.str[STRING_SSL_CAPATH]:
            "none");
    }
  
    /* Load the client certificate, and private key */
    if(data->set.str[STRING_CERT] && data->set.str[STRING_KEY]) {
      int file_type = do_file_type(data->set.str[STRING_CERT_TYPE]);
  
      if(SSL_CTX_use_certificate_file(conssl->ctx, data->set.str[STRING_CERT],
                                       file_type) != 1) {
        failf(data, "unable to use client certificate (no key or wrong pass"
              " phrase?)");
        return CURLE_SSL_CONNECT_ERROR;
      }
  
      file_type = do_file_type(data->set.str[STRING_KEY_TYPE]);
      if(SSL_CTX_use_PrivateKey_file(conssl->ctx, data->set.str[STRING_KEY],
                                      file_type) != 1) {
        failf(data, "unable to set private key");
        return CURLE_SSL_CONNECT_ERROR;
      }
    }
  #else
    if(CyaSSL_no_filesystem_verify(conssl->ctx)!= SSL_SUCCESS) {
      return CURLE_SSL_CONNECT_ERROR;
    }
  #endif /* NO_FILESYSTEM */
  
    /* SSL always tries to verify the peer, this only says whether it should
     * fail to connect if the verification fails, or if it should continue
     * anyway. In the latter case the result of the verification is checked with
     * SSL_get_verify_result() below. */
    SSL_CTX_set_verify(conssl->ctx,
                       data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE,
                       NULL);
  
    /* Let's make an SSL structure */
    if(conssl->handle)
      SSL_free(conssl->handle);
    conssl->handle = SSL_new(conssl->ctx);
    if(!conssl->handle) {
      failf(data, "SSL: couldn't create a context (handle)!");
      return CURLE_OUT_OF_MEMORY;
    }
  
    /* Check if there's a cached ID we can/should use here! */
    if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, NULL)) {
      /* we got a session id, use it! */
      if(!SSL_set_session(conssl->handle, ssl_sessionid)) {
        failf(data, "SSL: SSL_set_session failed: %s",
              ERR_error_string(SSL_get_error(conssl->handle, 0),NULL));
        return CURLE_SSL_CONNECT_ERROR;
      }
      /* Informational message */
      infof (data, "SSL re-using session ID\n");
    }
  
    /* pass the raw socket into the SSL layer */
    if(!SSL_set_fd(conssl->handle, (int)sockfd)) {
      failf(data, "SSL: SSL_set_fd failed");
      return CURLE_SSL_CONNECT_ERROR;
    }
  
    conssl->connecting_state = ssl_connect_2;
    return CURLE_OK;
  }
  
  
  static CURLcode
  cyassl_connect_step2(struct connectdata *conn,
                       int sockindex)
  {
    int ret = -1;
    struct SessionHandle *data = conn->data;
    struct ssl_connect_data* conssl = &conn->ssl[sockindex];
  
    infof(data, "CyaSSL: Connecting to %s:%d\n",
          conn->host.name, conn->remote_port);
  
    conn->recv[sockindex] = cyassl_recv;
    conn->send[sockindex] = cyassl_send;
  
    ret = SSL_connect(conssl->handle);
    if(ret != 1) {
      char error_buffer[80];
      int  detail = SSL_get_error(conssl->handle, ret);
  
      if(SSL_ERROR_WANT_READ == detail) {
        conssl->connecting_state = ssl_connect_2_reading;
        return CURLE_OK;
      }
  
      if(SSL_ERROR_WANT_WRITE == detail) {
        conssl->connecting_state = ssl_connect_2_writing;
        return CURLE_OK;
      }
  
      failf(data, "SSL_connect failed with error %d: %s", detail,
            ERR_error_string(detail, error_buffer));
      return CURLE_SSL_CONNECT_ERROR;
    }
  
    conssl->connecting_state = ssl_connect_3;
    infof(data, "SSL connected");
  
    return CURLE_OK;
  }
  
  
  static CURLcode
  cyassl_connect_step3(struct connectdata *conn,
                       int sockindex)
  {
    CURLcode retcode = CURLE_OK;
    void *old_ssl_sessionid=NULL;
    struct SessionHandle *data = conn->data;
    struct ssl_connect_data *connssl = &conn->ssl[sockindex];
    int incache;
    SSL_SESSION *our_ssl_sessionid;
  
    DEBUGASSERT(ssl_connect_3 == connssl->connecting_state);
  
    our_ssl_sessionid = SSL_get_session(connssl->handle);
  
    incache = !(Curl_ssl_getsessionid(conn, &old_ssl_sessionid, NULL));
    if(incache) {
      if(old_ssl_sessionid != our_ssl_sessionid) {
        infof(data, "old SSL session ID is stale, removing\n");
        Curl_ssl_delsessionid(conn, old_ssl_sessionid);
        incache = FALSE;
      }
    }
    if(!incache) {
      retcode = Curl_ssl_addsessionid(conn, our_ssl_sessionid,
                                      0 /* unknown size */);
      if(retcode) {
        failf(data, "failed to store ssl session");
        return retcode;
      }
    }
  
    connssl->connecting_state = ssl_connect_done;
  
    return retcode;
  }
  
  
  static ssize_t cyassl_send(struct connectdata *conn,
                             int sockindex,
                             const void *mem,
                             size_t len,
                             CURLcode *curlcode)
  {
    char error_buffer[80];
    int  memlen = (len > (size_t)INT_MAX) ? INT_MAX : (int)len;
    int  rc     = SSL_write(conn->ssl[sockindex].handle, mem, memlen);
  
    if(rc < 0) {
      int err = SSL_get_error(conn->ssl[sockindex].handle, rc);
  
      switch(err) {
      case SSL_ERROR_WANT_READ:
      case SSL_ERROR_WANT_WRITE:
        /* there's data pending, re-invoke SSL_write() */
        *curlcode = CURLE_AGAIN;
        return -1;
      default:
        failf(conn->data, "SSL write: %s, errno %d",
              ERR_error_string(err, error_buffer),
              SOCKERRNO);
        *curlcode = CURLE_SEND_ERROR;
        return -1;
      }
    }
    return rc;
  }
  
  void Curl_cyassl_close_all(struct SessionHandle *data)
  {
    (void)data;
  }
  
  void Curl_cyassl_close(struct connectdata *conn, int sockindex)
  {
    struct ssl_connect_data *conssl = &conn->ssl[sockindex];
  
    if(conssl->handle) {
      (void)SSL_shutdown(conssl->handle);
      SSL_free (conssl->handle);
      conssl->handle = NULL;
    }
    if(conssl->ctx) {
      SSL_CTX_free (conssl->ctx);
      conssl->ctx = NULL;
    }
  }
  
  static ssize_t cyassl_recv(struct connectdata *conn,
                             int num,
                             char *buf,
                             size_t buffersize,
                             CURLcode *curlcode)
  {
    char error_buffer[80];
    int  buffsize = (buffersize > (size_t)INT_MAX) ? INT_MAX : (int)buffersize;
    int  nread    = SSL_read(conn->ssl[num].handle, buf, buffsize);
  
    if(nread < 0) {
      int err = SSL_get_error(conn->ssl[num].handle, nread);
  
      switch(err) {
      case SSL_ERROR_ZERO_RETURN: /* no more data */
        break;
      case SSL_ERROR_WANT_READ:
      case SSL_ERROR_WANT_WRITE:
        /* there's data pending, re-invoke SSL_read() */
        *curlcode = CURLE_AGAIN;
        return -1;
      default:
        failf(conn->data, "SSL read: %s, errno %d",
              ERR_error_string(err, error_buffer),
              SOCKERRNO);
        *curlcode = CURLE_RECV_ERROR;
        return -1;
      }
    }
    return nread;
  }
  
  
  void Curl_cyassl_session_free(void *ptr)
  {
    (void)ptr;
    /* CyaSSL reuses sessions on own, no free */
  }
  
  
  size_t Curl_cyassl_version(char *buffer, size_t size)
  {
  #ifdef CYASSL_VERSION
    return snprintf(buffer, size, "CyaSSL/%s", CYASSL_VERSION);
  #else
    return snprintf(buffer, size, "CyaSSL/%s", "<1.8.8");
  #endif
  }
  
  
  int Curl_cyassl_init(void)
  {
    InitCyaSSL();
  
    return 1;
  }
  
  
  bool Curl_cyassl_data_pending(const struct connectdata* conn, int connindex)
  {
    if(conn->ssl[connindex].handle)   /* SSL is in use */
      return (0 != SSL_pending(conn->ssl[connindex].handle)) ? TRUE : FALSE;
    else
      return FALSE;
  }
  
  
  /*
   * This function is called to shut down the SSL layer but keep the
   * socket open (CCC - Clear Command Channel)
   */
  int Curl_cyassl_shutdown(struct connectdata *conn, int sockindex)
  {
    int retval = 0;
    struct ssl_connect_data *connssl = &conn->ssl[sockindex];
  
    if(connssl->handle) {
      SSL_free (connssl->handle);
      connssl->handle = NULL;
    }
    return retval;
  }
  
  
  static CURLcode
  cyassl_connect_common(struct connectdata *conn,
                        int sockindex,
                        bool nonblocking,
                        bool *done)
  {
    CURLcode retcode;
    struct SessionHandle *data = conn->data;
    struct ssl_connect_data *connssl = &conn->ssl[sockindex];
    curl_socket_t sockfd = conn->sock[sockindex];
    long timeout_ms;
    int what;
  
    /* check if the connection has already been established */
    if(ssl_connection_complete == connssl->state) {
      *done = TRUE;
      return CURLE_OK;
    }
  
    if(ssl_connect_1==connssl->connecting_state) {
      /* Find out how much more time we're allowed */
      timeout_ms = Curl_timeleft(data, NULL, TRUE);
  
      if(timeout_ms < 0) {
        /* no need to continue if time already is up */
        failf(data, "SSL connection timeout");
        return CURLE_OPERATION_TIMEDOUT;
      }
      retcode = cyassl_connect_step1(conn, sockindex);
      if(retcode)
        return retcode;
    }
  
    while(ssl_connect_2 == connssl->connecting_state ||
          ssl_connect_2_reading == connssl->connecting_state ||
          ssl_connect_2_writing == connssl->connecting_state) {
  
      /* check allowed time left */
      timeout_ms = Curl_timeleft(data, NULL, TRUE);
  
      if(timeout_ms < 0) {
        /* no need to continue if time already is up */
        failf(data, "SSL connection timeout");
        return CURLE_OPERATION_TIMEDOUT;
      }
  
      /* if ssl is expecting something, check if it's available. */
      if(connssl->connecting_state == ssl_connect_2_reading
         || connssl->connecting_state == ssl_connect_2_writing) {
  
        curl_socket_t writefd = ssl_connect_2_writing==
          connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
        curl_socket_t readfd = ssl_connect_2_reading==
          connssl->connecting_state?sockfd:CURL_SOCKET_BAD;
  
        what = Curl_socket_ready(readfd, writefd, nonblocking?0:timeout_ms);
        if(what < 0) {
          /* fatal error */
          failf(data, "select/poll on SSL socket, errno: %d", SOCKERRNO);
          return CURLE_SSL_CONNECT_ERROR;
        }
        else if(0 == what) {
          if(nonblocking) {
            *done = FALSE;
            return CURLE_OK;
          }
          else {
            /* timeout */
            failf(data, "SSL connection timeout");
            return CURLE_OPERATION_TIMEDOUT;
          }
        }
        /* socket is readable or writable */
      }
  
      /* Run transaction, and return to the caller if it failed or if
       * this connection is part of a multi handle and this loop would
       * execute again. This permits the owner of a multi handle to
       * abort a connection attempt before step2 has completed while
       * ensuring that a client using select() or epoll() will always
       * have a valid fdset to wait on.
       */
      retcode = cyassl_connect_step2(conn, sockindex);
      if(retcode || (nonblocking &&
                     (ssl_connect_2 == connssl->connecting_state ||
                      ssl_connect_2_reading == connssl->connecting_state ||
                      ssl_connect_2_writing == connssl->connecting_state)))
        return retcode;
  
    } /* repeat step2 until all transactions are done. */
  
    if(ssl_connect_3==connssl->connecting_state) {
      retcode = cyassl_connect_step3(conn, sockindex);
      if(retcode)
        return retcode;
    }
  
    if(ssl_connect_done==connssl->connecting_state) {
      connssl->state = ssl_connection_complete;
      conn->recv[sockindex] = cyassl_recv;
      conn->send[sockindex] = cyassl_send;
      *done = TRUE;
    }
    else
      *done = FALSE;
  
    /* Reset our connect state machine */
    connssl->connecting_state = ssl_connect_1;
  
    return CURLE_OK;
  }
  
  
  CURLcode
  Curl_cyassl_connect_nonblocking(struct connectdata *conn,
                                  int sockindex,
                                  bool *done)
  {
    return cyassl_connect_common(conn, sockindex, TRUE, done);
  }
  
  
  CURLcode
  Curl_cyassl_connect(struct connectdata *conn,
                      int sockindex)
  {
    CURLcode retcode;
    bool done = FALSE;
  
    retcode = cyassl_connect_common(conn, sockindex, FALSE, &done);
    if(retcode)
      return retcode;
  
    DEBUGASSERT(done);
  
    return CURLE_OK;
  }
  
  #endif
  