/* this function does a SSL/TLS (re-)handshake */ static CURLcode handshake(struct connectdata *conn, int sockindex, bool duringconnect, bool nonblocking) { struct SessionHandle *data = conn->data; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; gnutls_session session = conn->ssl[sockindex].session; curl_socket_t sockfd = conn->sock[sockindex]; long timeout_ms; int rc; int what; for(;;) { /* check allowed time left */ timeout_ms = Curl_timeleft(data, NULL, duringconnect); 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?timeout_ms:1000); 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) return CURLE_OK; else if(timeout_ms) { /* timeout */ failf(data, "SSL connection timeout at %ld", timeout_ms); return CURLE_OPERATION_TIMEDOUT; } } /* socket is readable or writable */ } rc = gnutls_handshake(session); if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { connssl->connecting_state = gnutls_record_get_direction(session)? ssl_connect_2_writing:ssl_connect_2_reading; continue; if(nonblocking) return CURLE_OK; } else if((rc < 0) && !gnutls_error_is_fatal(rc)) { const char *strerr = NULL; if(rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { int alert = gnutls_alert_get(session); strerr = gnutls_alert_get_name(alert); } if(strerr == NULL) strerr = gnutls_strerror(rc); failf(data, "gnutls_handshake() warning: %s", strerr); } else if(rc < 0) { const char *strerr = NULL; if(rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { int alert = gnutls_alert_get(session); strerr = gnutls_alert_get_name(alert); } if(strerr == NULL) strerr = gnutls_strerror(rc); failf(data, "gnutls_handshake() failed: %s", strerr); return CURLE_SSL_CONNECT_ERROR; } /* Reset our connect state machine */ connssl->connecting_state = ssl_connect_1; return CURLE_OK; } }
static apr_status_t gnutls_io_input_read(mgs_handle_t * ctxt, char *buf, apr_size_t * len) { apr_size_t wanted = *len; apr_size_t bytes = 0; int rc; *len = 0; /* If we have something leftover from last time, try that first. */ if ((bytes = char_buffer_read(&ctxt->input_cbuf, buf, wanted))) { *len = bytes; if (ctxt->input_mode == AP_MODE_SPECULATIVE) { /* We want to rollback this read. */ if (ctxt->input_cbuf.length > 0) { ctxt->input_cbuf.value -= bytes; ctxt->input_cbuf.length += bytes; } else { char_buffer_write(&ctxt->input_cbuf, buf, (int) bytes); } return APR_SUCCESS; } /* This could probably be *len == wanted, but be safe from stray * photons. */ if (*len >= wanted) { return APR_SUCCESS; } if (ctxt->input_mode == AP_MODE_GETLINE) { if (memchr(buf, APR_ASCII_LF, *len)) { return APR_SUCCESS; } } else { /* Down to a nonblock pattern as we have some data already */ ctxt->input_block = APR_NONBLOCK_READ; } } if (ctxt->session == NULL) { return APR_EGENERAL; } while (1) { rc = gnutls_record_recv(ctxt->session, buf + bytes, wanted - bytes); if (rc > 0) { *len += rc; if (ctxt->input_mode == AP_MODE_SPECULATIVE) { /* We want to rollback this read. */ char_buffer_write(&ctxt->input_cbuf, buf, rc); } return ctxt->input_rc; } else if (rc == 0) { /* If EAGAIN, we will loop given a blocking read, * otherwise consider ourselves at EOF. */ if (APR_STATUS_IS_EAGAIN(ctxt->input_rc) || APR_STATUS_IS_EINTR(ctxt->input_rc)) { /* Already read something, return APR_SUCCESS instead. * On win32 in particular, but perhaps on other kernels, * a blocking call isn't 'always' blocking. */ if (*len > 0) { ctxt->input_rc = APR_SUCCESS; break; } if (ctxt->input_block == APR_NONBLOCK_READ) { break; } } else { if (*len > 0) { ctxt->input_rc = APR_SUCCESS; } else { ctxt->input_rc = APR_EOF; } break; } } else { /* (rc < 0) */ if (rc == GNUTLS_E_REHANDSHAKE) { /* A client has asked for a new Hankshake. Currently, we don't do it */ ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc, ctxt->c->base_server, "GnuTLS: Error reading data. Client Requested a New Handshake." " (%d) '%s'", rc, gnutls_strerror(rc)); } else if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED) { rc = gnutls_alert_get(ctxt->session); ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc, ctxt->c->base_server, "GnuTLS: Warning Alert From Client: " " (%d) '%s'", rc, gnutls_alert_get_name(rc)); } else if (rc == GNUTLS_E_FATAL_ALERT_RECEIVED) { rc = gnutls_alert_get(ctxt->session); ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc, ctxt->c->base_server, "GnuTLS: Fatal Alert From Client: " "(%d) '%s'", rc, gnutls_alert_get_name(rc)); ctxt->input_rc = APR_EGENERAL; break; } else { /* Some Other Error. Report it. Die. */ if (gnutls_error_is_fatal(rc)) { ap_log_error(APLOG_MARK, APLOG_INFO, ctxt->input_rc, ctxt->c->base_server, "GnuTLS: Error reading data. (%d) '%s'", rc, gnutls_strerror(rc)); } else if (*len > 0) { ctxt->input_rc = APR_SUCCESS; break; } } if (ctxt->input_rc == APR_SUCCESS) { ctxt->input_rc = APR_EGENERAL; } break; } } return ctxt->input_rc; }
/* This function will check if the received record type is * the one we actually expect. */ static int record_check_type (gnutls_session_t session, content_type_t recv_type, content_type_t type, gnutls_handshake_description_t htype, opaque * data, int data_size) { int ret; if ((recv_type == type) && (type == GNUTLS_APPLICATION_DATA || type == GNUTLS_HANDSHAKE || type == GNUTLS_INNER_APPLICATION)) { _gnutls_record_buffer_put (type, session, (void *) data, data_size); } else { switch (recv_type) { case GNUTLS_ALERT: _gnutls_record_log ("REC[%x]: Alert[%d|%d] - %s - was received\n", session, data[0], data[1], gnutls_alert_get_name ((int) data[1])); session->internals.last_alert = data[1]; /* if close notify is received and * the alert is not fatal */ if (data[1] == GNUTLS_A_CLOSE_NOTIFY && data[0] != GNUTLS_AL_FATAL) { /* If we have been expecting for an alert do */ session->internals.read_eof = 1; return GNUTLS_E_INT_RET_0; /* EOF */ } else { /* if the alert is FATAL or WARNING * return the apropriate message */ gnutls_assert (); ret = GNUTLS_E_WARNING_ALERT_RECEIVED; if (data[0] == GNUTLS_AL_FATAL) { session_unresumable (session); session_invalidate (session); ret = GNUTLS_E_FATAL_ALERT_RECEIVED; } return ret; } break; case GNUTLS_CHANGE_CIPHER_SPEC: /* this packet is now handled in the recv_int() * function */ gnutls_assert (); return GNUTLS_E_UNEXPECTED_PACKET; case GNUTLS_APPLICATION_DATA: /* even if data is unexpected put it into the buffer */ if ((ret = _gnutls_record_buffer_put (recv_type, session, (void *) data, data_size)) < 0) { gnutls_assert (); return ret; } /* the got_application data is only returned * if expecting client hello (for rehandshake * reasons). Otherwise it is an unexpected packet */ if (type == GNUTLS_ALERT || (htype == GNUTLS_HANDSHAKE_CLIENT_HELLO && type == GNUTLS_HANDSHAKE)) return GNUTLS_E_GOT_APPLICATION_DATA; else { gnutls_assert (); return GNUTLS_E_UNEXPECTED_PACKET; } break; case GNUTLS_HANDSHAKE: /* This is legal if HELLO_REQUEST is received - and we are a client. * If we are a server, a client may initiate a renegotiation at any time. */ if (session->security_parameters.entity == GNUTLS_SERVER) { gnutls_assert (); return GNUTLS_E_REHANDSHAKE; } /* If we are already in a handshake then a Hello * Request is illegal. But here we don't really care * since this message will never make it up here. */ /* So we accept it */ return _gnutls_recv_hello_request (session, data, data_size); break; case GNUTLS_INNER_APPLICATION: /* even if data is unexpected put it into the buffer */ if ((ret = _gnutls_record_buffer_put (recv_type, session, (void *) data, data_size)) < 0) { gnutls_assert (); return ret; } gnutls_assert (); return GNUTLS_E_UNEXPECTED_PACKET; break; default: _gnutls_record_log ("REC[%x]: Received Unknown packet %d expecting %d\n", session, recv_type, type); gnutls_assert (); return GNUTLS_E_INTERNAL_ERROR; } } return 0; }