/* * Helper read-from-socket functions. Does the same as Curl_read() but it * blocks until all bytes amount of buffersize will be read. No more, no less. * * This is STUPID BLOCKING behaviour which we frown upon, but right now this * is what we have... */ static int blockread_all(struct connectdata *conn, /* connection data */ curl_socket_t sockfd, /* read from this socket */ char *buf, /* store read data here */ ssize_t buffersize, /* max amount to read */ ssize_t *n, /* amount bytes read */ long conn_timeout) /* timeout for data wait relative to conn->created */ { ssize_t nread; ssize_t allread = 0; int result; struct timeval tvnow; long conntime; *n = 0; do { tvnow = Curl_tvnow(); /* calculating how long connection is establishing */ conntime = Curl_tvdiff(tvnow, conn->created); if(conntime > conn_timeout) { /* we already got the timeout */ result = ~CURLE_OK; break; } if(Curl_select(sockfd, CURL_SOCKET_BAD, (int)(conn_timeout - conntime)) <= 0) { result = ~CURLE_OK; break; } result = Curl_read(conn, sockfd, buf, buffersize, &nread); if(result) break; if(buffersize == nread) { allread += nread; *n = allread; result = CURLE_OK; break; } buffersize -= nread; buf += nread; allread += nread; } while(1); return result; }
/*************************************************************************** * This function is still only for testing purposes. It makes a great way * to run the full test suite on the multi interface instead of the easy one. *************************************************************************** * * The *new* curl_easy_perform() is the external interface that performs a * transfer previously setup. * * Wrapper-function that: creates a multi handle, adds the easy handle to it, * runs curl_multi_perform() until the transfer is done, then detaches the * easy handle, destroys the multi handle and returns the easy handle's return * code. This will make everything internally use and assume multi interface. */ CURLcode curl_easy_perform(CURL *easy) { CURLM *multi; CURLMcode mcode; CURLcode code = CURLE_OK; int still_running; struct timeval timeout; int rc; CURLMsg *msg; fd_set fdread; fd_set fdwrite; fd_set fdexcep; int maxfd; if(!easy) return CURLE_BAD_FUNCTION_ARGUMENT; multi = curl_multi_init(); if(!multi) return CURLE_OUT_OF_MEMORY; mcode = curl_multi_add_handle(multi, easy); if(mcode) { curl_multi_cleanup(multi); if(mcode == CURLM_OUT_OF_MEMORY) return CURLE_OUT_OF_MEMORY; else return CURLE_FAILED_INIT; } /* we start some action by calling perform right away */ do { while(CURLM_CALL_MULTI_PERFORM == curl_multi_perform(multi, &still_running)); if(!still_running) break; FD_ZERO(&fdread); FD_ZERO(&fdwrite); FD_ZERO(&fdexcep); /* timeout once per second */ timeout.tv_sec = 1; timeout.tv_usec = 0; /* Old deprecated style: get file descriptors from the transfers */ curl_multi_fdset(multi, &fdread, &fdwrite, &fdexcep, &maxfd); rc = Curl_select(maxfd+1, &fdread, &fdwrite, &fdexcep, &timeout); /* The way is to extract the sockets and wait for them without using select. This whole alternative version should probably rather use the curl_multi_socket() approach. */ if(rc == -1) /* select error */ break; /* timeout or data to send/receive => loop! */ } while(still_running); msg = curl_multi_info_read(multi, &rc); if(msg) code = msg->data.result; mcode = curl_multi_remove_handle(multi, easy); /* what to do if it fails? */ mcode = curl_multi_cleanup(multi); /* what to do if it fails? */ return code; }
/* * This function is called to shut down the SSL layer but keep the * socket open (CCC - Clear Command Channel) */ int Curl_gtls_shutdown(struct connectdata *conn, int sockindex) { int result; int retval = 0; struct SessionHandle *data = conn->data; int done = 0; ssize_t nread; char buf[120]; /* This has only been tested on the proftpd server, and the mod_tls code sends a close notify alert without waiting for a close notify alert in response. Thus we wait for a close notify alert from the server, but we do not send one. Let's hope other servers do the same... */ if(conn->ssl[sockindex].session) { while(!done) { int what = Curl_select(conn->sock[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 */ result = gnutls_record_recv(conn->ssl[sockindex].session, buf, sizeof(buf)); switch(result) { case 0: /* This is the expected response. There was no data but only the close notify alert */ done = 1; break; case GNUTLS_E_AGAIN: case GNUTLS_E_INTERRUPTED: infof(data, "GNUTLS_E_AGAIN || GNUTLS_E_INTERRUPTED\n"); break; default: retval = -1; done = 1; break; } } else if(0 == what) { /* timeout */ failf(data, "SSL shutdown timeout"); done = 1; break; } else { /* anything that gets here is fatally bad */ failf(data, "select on SSL socket, errno: %d", Curl_sockerrno()); retval = -1; done = 1; } } gnutls_deinit(conn->ssl[sockindex].session); } gnutls_certificate_free_credentials(conn->ssl[sockindex].cred); conn->ssl[sockindex].session = NULL; conn->ssl[sockindex].use = FALSE; return retval; }
/* this function does a BLOCKING SSL/TLS (re-)handshake */ static CURLcode handshake(struct connectdata *conn, gnutls_session session, int sockindex, bool duringconnect) { struct SessionHandle *data = conn->data; int rc; do { rc = gnutls_handshake(session); if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { long timeout_ms = DEFAULT_CONNECT_TIMEOUT; long has_passed; if(duringconnect && data->set.connecttimeout) timeout_ms = data->set.connecttimeout*1000; if(data->set.timeout) { /* get the strictest timeout of the ones converted to milliseconds */ if((data->set.timeout*1000) < timeout_ms) timeout_ms = data->set.timeout*1000; } /* Evaluate in milliseconds how much time that has passed */ has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle); /* subtract the passed time */ timeout_ms -= has_passed; if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ failf(data, "SSL connection timeout"); return CURLE_OPERATION_TIMEOUTED; } rc = Curl_select(conn->sock[sockindex], conn->sock[sockindex], (int)timeout_ms); if(rc > 0) /* reabable or writable, go loop*/ continue; else if(0 == rc) { /* timeout */ failf(data, "SSL connection timeout"); return CURLE_OPERATION_TIMEDOUT; } else { /* anything that gets here is fatally bad */ failf(data, "select on SSL socket, errno: %d", Curl_sockerrno()); return CURLE_SSL_CONNECT_ERROR; } } else break; } while(1); if (rc < 0) { failf(data, "gnutls_handshake() failed: %s", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } return CURLE_OK; }
/* * This function logs in to a SOCKS5 proxy and sends the specifics to the final * destination server. */ CURLcode Curl_SOCKS5(const char *proxy_name, const char *proxy_password, struct connectdata *conn) { /* According to the RFC1928, section "6. Replies". This is what a SOCK5 replies: +----+-----+-------+------+----------+----------+ |VER | REP | RSV | ATYP | BND.ADDR | BND.PORT | +----+-----+-------+------+----------+----------+ | 1 | 1 | X'00' | 1 | Variable | 2 | +----+-----+-------+------+----------+----------+ Where: o VER protocol version: X'05' o REP Reply field: o X'00' succeeded */ unsigned char socksreq[600]; /* room for large user/pw (255 max each) */ ssize_t actualread; ssize_t written; int result; CURLcode code; curl_socket_t sock = conn->sock[FIRSTSOCKET]; struct SessionHandle *data = conn->data; long timeout; /* get timeout */ if(data->set.timeout && data->set.connecttimeout) { if (data->set.timeout < data->set.connecttimeout) timeout = data->set.timeout*1000; else timeout = data->set.connecttimeout*1000; } else if(data->set.timeout) timeout = data->set.timeout*1000; else if(data->set.connecttimeout) timeout = data->set.connecttimeout*1000; else timeout = DEFAULT_CONNECT_TIMEOUT; Curl_nonblock(sock, TRUE); /* wait until socket gets connected */ result = Curl_select(CURL_SOCKET_BAD, sock, (int)timeout); if(-1 == result) { failf(conn->data, "SOCKS5: no connection here"); return CURLE_COULDNT_CONNECT; } else if(0 == result) { failf(conn->data, "SOCKS5: connection timeout"); return CURLE_OPERATION_TIMEDOUT; } if(result & CSELECT_ERR) { failf(conn->data, "SOCKS5: error occured during connection"); return CURLE_COULDNT_CONNECT; } socksreq[0] = 5; /* version */ socksreq[1] = (char)(proxy_name ? 2 : 1); /* number of methods (below) */ socksreq[2] = 0; /* no authentication */ socksreq[3] = 2; /* username/password */ Curl_nonblock(sock, FALSE); code = Curl_write(conn, sock, (char *)socksreq, (2 + (int)socksreq[1]), &written); if ((code != CURLE_OK) || (written != (2 + (int)socksreq[1]))) { failf(data, "Unable to send initial SOCKS5 request."); return CURLE_COULDNT_CONNECT; } Curl_nonblock(sock, TRUE); result = Curl_select(sock, CURL_SOCKET_BAD, (int)timeout); if(-1 == result) { failf(conn->data, "SOCKS5 nothing to read"); return CURLE_COULDNT_CONNECT; } else if(0 == result) { failf(conn->data, "SOCKS5 read timeout"); return CURLE_OPERATION_TIMEDOUT; } if(result & CSELECT_ERR) { failf(conn->data, "SOCKS5 read error occured"); return CURLE_RECV_ERROR; } Curl_nonblock(sock, FALSE); result=blockread_all(conn, sock, (char *)socksreq, 2, &actualread, timeout); if ((result != CURLE_OK) || (actualread != 2)) { failf(data, "Unable to receive initial SOCKS5 response."); return CURLE_COULDNT_CONNECT; } if (socksreq[0] != 5) { failf(data, "Received invalid version in initial SOCKS5 response."); return CURLE_COULDNT_CONNECT; } if (socksreq[1] == 0) { /* Nothing to do, no authentication needed */ ; } else if (socksreq[1] == 2) { /* Needs user name and password */ size_t userlen, pwlen; int len; if(proxy_name && proxy_password) { userlen = strlen(proxy_name); pwlen = proxy_password?strlen(proxy_password):0; } else { userlen = 0; pwlen = 0; } /* username/password request looks like * +----+------+----------+------+----------+ * |VER | ULEN | UNAME | PLEN | PASSWD | * +----+------+----------+------+----------+ * | 1 | 1 | 1 to 255 | 1 | 1 to 255 | * +----+------+----------+------+----------+ */ len = 0; socksreq[len++] = 1; /* username/pw subnegotiation version */ socksreq[len++] = (char) userlen; memcpy(socksreq + len, proxy_name, (int) userlen); len += userlen; socksreq[len++] = (char) pwlen; memcpy(socksreq + len, proxy_password, (int) pwlen); len += pwlen; code = Curl_write(conn, sock, (char *)socksreq, len, &written); if ((code != CURLE_OK) || (len != written)) { failf(data, "Failed to send SOCKS5 sub-negotiation request."); return CURLE_COULDNT_CONNECT; } result=blockread_all(conn, sock, (char *)socksreq, 2, &actualread, timeout); if ((result != CURLE_OK) || (actualread != 2)) { failf(data, "Unable to receive SOCKS5 sub-negotiation response."); return CURLE_COULDNT_CONNECT; } /* ignore the first (VER) byte */ if (socksreq[1] != 0) { /* status */ failf(data, "User was rejected by the SOCKS5 server (%d %d).", socksreq[0], socksreq[1]); return CURLE_COULDNT_CONNECT; } /* Everything is good so far, user was authenticated! */ } else { /* error */ if (socksreq[1] == 1) { failf(data, "SOCKS5 GSSAPI per-message authentication is not supported."); return CURLE_COULDNT_CONNECT; } else if (socksreq[1] == 255) { if (!proxy_name || !*proxy_name) { failf(data, "No authentication method was acceptable. (It is quite likely" " that the SOCKS5 server wanted a username/password, since none" " was supplied to the server on this connection.)"); } else { failf(data, "No authentication method was acceptable."); } return CURLE_COULDNT_CONNECT; } else { failf(data, "Undocumented SOCKS5 mode attempted to be used by server."); return CURLE_COULDNT_CONNECT; } } /* Authentication is complete, now specify destination to the proxy */ socksreq[0] = 5; /* version (SOCKS5) */ socksreq[1] = 1; /* connect */ socksreq[2] = 0; /* must be zero */ socksreq[3] = 1; /* IPv4 = 1 */ { struct Curl_dns_entry *dns; Curl_addrinfo *hp=NULL; int rc = Curl_resolv(conn, conn->host.name, (int)conn->remote_port, &dns); if(rc == CURLRESOLV_ERROR) return CURLE_COULDNT_RESOLVE_HOST; if(rc == CURLRESOLV_PENDING) /* this requires that we're in "wait for resolve" state */ rc = Curl_wait_for_resolv(conn, &dns); /* * We cannot use 'hostent' as a struct that Curl_resolv() returns. It * returns a Curl_addrinfo pointer that may not always look the same. */ if(dns) hp=dns->addr; if (hp) { char buf[64]; unsigned short ip[4]; Curl_printable_address(hp, buf, sizeof(buf)); if(4 == sscanf( buf, "%hu.%hu.%hu.%hu", &ip[0], &ip[1], &ip[2], &ip[3])) { socksreq[4] = (unsigned char)ip[0]; socksreq[5] = (unsigned char)ip[1]; socksreq[6] = (unsigned char)ip[2]; socksreq[7] = (unsigned char)ip[3]; } else hp = NULL; /* fail! */ Curl_resolv_unlock(data, dns); /* not used anymore from now on */ } if(!hp) { failf(data, "Failed to resolve \"%s\" for SOCKS5 connect.", conn->host.name); return CURLE_COULDNT_RESOLVE_HOST; } } *((unsigned short*)&socksreq[8]) = htons(conn->remote_port); { const int packetsize = 10; code = Curl_write(conn, sock, (char *)socksreq, packetsize, &written); if ((code != CURLE_OK) || (written != packetsize)) { failf(data, "Failed to send SOCKS5 connect request."); return CURLE_COULDNT_CONNECT; } result = blockread_all(conn, sock, (char *)socksreq, packetsize, &actualread, timeout); if ((result != CURLE_OK) || (actualread != packetsize)) { failf(data, "Failed to receive SOCKS5 connect request ack."); return CURLE_COULDNT_CONNECT; } if (socksreq[0] != 5) { /* version */ failf(data, "SOCKS5 reply has wrong version, version should be 5."); return CURLE_COULDNT_CONNECT; } if (socksreq[1] != 0) { /* Anything besides 0 is an error */ failf(data, "Can't complete SOCKS5 connection to %d.%d.%d.%d:%d. (%d)", (unsigned char)socksreq[4], (unsigned char)socksreq[5], (unsigned char)socksreq[6], (unsigned char)socksreq[7], (unsigned int)ntohs(*(unsigned short*)(&socksreq[8])), socksreq[1]); return CURLE_COULDNT_CONNECT; } } Curl_nonblock(sock, TRUE); return CURLE_OK; /* Proxy was successful! */ }
/* ====================================================== */ CURLcode Curl_ossl_connect(struct connectdata *conn, int sockindex) { CURLcode retcode = CURLE_OK; struct SessionHandle *data = conn->data; int err; long lerr; int what; char * str; SSL_METHOD_QUAL SSL_METHOD *req_method=NULL; void *ssl_sessionid=NULL; ASN1_TIME *certdate; curl_socket_t sockfd = conn->sock[sockindex]; struct ssl_connect_data *connssl = &conn->ssl[sockindex]; if(!ssl_seeded || data->set.ssl.random_file || data->set.ssl.egdsocket) { /* Make funny stuff to get random input */ random_the_seed(data); ssl_seeded = TRUE; } /* check to see if we've been told to use an explicit SSL/TLS version */ switch(data->set.ssl.version) { default: 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_SSLv2: req_method = SSLv2_client_method(); break; case CURL_SSLVERSION_SSLv3: req_method = SSLv3_client_method(); break; } connssl->ctx = SSL_CTX_new(req_method); if(!connssl->ctx) { failf(data, "SSL: couldn't create a context!"); return CURLE_OUT_OF_MEMORY; } #ifdef SSL_CTRL_SET_MSG_CALLBACK if (data->set.fdebug) { /* the SSL trace callback is only used for verbose logging so we only inform about failures of setting it */ if (!SSL_CTX_callback_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK, (void (*)(void))ssl_tls_trace)) { infof(data, "SSL: couldn't set callback!"); } else if (!SSL_CTX_ctrl(connssl->ctx, SSL_CTRL_SET_MSG_CALLBACK_ARG, 0, conn)) { infof(data, "SSL: couldn't set callback argument!"); } } #endif /* OpenSSL contains code to work-around lots of bugs and flaws in various SSL-implementations. SSL_CTX_set_options() is used to enabled those work-arounds. The man page for this option states that SSL_OP_ALL enables ll the work-arounds and that "It is usually safe to use SSL_OP_ALL to enable the bug workaround options if compatibility with somewhat broken implementations is desired." */ SSL_CTX_set_options(connssl->ctx, SSL_OP_ALL); #if 0 /* * Not sure it's needed to tell SSL_connect() that socket is * non-blocking. It doesn't seem to care, but just return with * SSL_ERROR_WANT_x. */ if (data->state.used_interface == Curl_if_multi) SSL_CTX_ctrl(connssl->ctx, BIO_C_SET_NBIO, 1, NULL); #endif if(data->set.cert) { if(!cert_stuff(conn, connssl->ctx, data->set.cert, data->set.cert_type, data->set.key, data->set.key_type)) { /* failf() is already done in cert_stuff() */ return CURLE_SSL_CERTPROBLEM; } } if(data->set.ssl.cipher_list) { if(!SSL_CTX_set_cipher_list(connssl->ctx, data->set.ssl.cipher_list)) { failf(data, "failed setting cipher list"); return CURLE_SSL_CIPHER; } } if (data->set.ssl.CAfile || data->set.ssl.CApath) { /* tell SSL where to find CA certificates that are used to verify the servers certificate. */ if (!SSL_CTX_load_verify_locations(connssl->ctx, data->set.ssl.CAfile, data->set.ssl.CApath)) { if (data->set.ssl.verifypeer) { /* Fail if we insist on successfully verifying the server. */ failf(data,"error setting certificate verify locations:\n" " CAfile: %s\n CApath: %s\n", data->set.ssl.CAfile ? data->set.ssl.CAfile : "none", data->set.ssl.CApath ? data->set.ssl.CApath : "none"); return CURLE_SSL_CACERT; } 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.ssl.CAfile ? data->set.ssl.CAfile : "none", data->set.ssl.CApath ? data->set.ssl.CApath : "none"); } /* 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(connssl->ctx, data->set.ssl.verifypeer?SSL_VERIFY_PEER:SSL_VERIFY_NONE, cert_verify_callback); /* give application a chance to interfere with SSL set up. */ if(data->set.ssl.fsslctx) { retcode = (*data->set.ssl.fsslctx)(data, connssl->ctx, data->set.ssl.fsslctxp); if(retcode) { failf(data,"error signaled by ssl ctx callback"); return retcode; } } /* Lets make an SSL structure */ connssl->handle = SSL_new(connssl->ctx); if (!connssl->handle) { failf(data, "SSL: couldn't create a context (handle)!"); return CURLE_OUT_OF_MEMORY; } SSL_set_connect_state(connssl->handle); connssl->server_cert = 0x0; /* 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(connssl->handle, ssl_sessionid)) { failf(data, "SSL: SSL_set_session failed: %s", ERR_error_string(ERR_get_error(),NULL)); return CURLE_SSL_CONNECT_ERROR; } /* Informational message */ infof (data, "SSL re-using session ID\n"); } /* pass the raw socket into the SSL layers */ if (!SSL_set_fd(connssl->handle, sockfd)) { failf(data, "SSL: SSL_set_fd failed: %s", ERR_error_string(ERR_get_error(),NULL)); return CURLE_SSL_CONNECT_ERROR; } while(1) { int writefd; int readfd; long timeout_ms; long has_passed; /* Find out if any timeout is set. If not, use 300 seconds. Otherwise, figure out the most strict timeout of the two possible one and then how much time that has elapsed to know how much time we allow for the connect call */ if(data->set.timeout || data->set.connecttimeout) { /* get the most strict timeout of the ones converted to milliseconds */ if(data->set.timeout && (data->set.timeout>data->set.connecttimeout)) timeout_ms = data->set.timeout*1000; else timeout_ms = data->set.connecttimeout*1000; } else /* no particular time-out has been set */ timeout_ms= DEFAULT_CONNECT_TIMEOUT; /* Evaluate in milliseconds how much time that has passed */ has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle); /* subtract the passed time */ timeout_ms -= has_passed; if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ failf(data, "SSL connection timeout"); return CURLE_OPERATION_TIMEOUTED; } readfd = CURL_SOCKET_BAD; writefd = CURL_SOCKET_BAD; err = SSL_connect(connssl->handle); /* 1 is fine 0 is "not successful but was shut down controlled" <0 is "handshake was not successful, because a fatal error occurred" */ if(1 != err) { int detail = SSL_get_error(connssl->handle, err); if(SSL_ERROR_WANT_READ == detail) readfd = sockfd; else if(SSL_ERROR_WANT_WRITE == detail) writefd = sockfd; else { /* untreated error */ unsigned long errdetail; char error_buffer[120]; /* OpenSSL documents that this must be at least 120 bytes long. */ CURLcode rc; const char *cert_problem = NULL; errdetail = ERR_get_error(); /* Gets the earliest error code from the thread's error queue and removes the entry. */ switch(errdetail) { case 0x1407E086: /* 1407E086: SSL routines: SSL2_SET_CERTIFICATE: certificate verify failed */ /* fall-through */ case 0x14090086: /* 14090086: SSL routines: SSL3_GET_SERVER_CERTIFICATE: certificate verify failed */ cert_problem = "SSL certificate problem, verify that the CA cert is" " OK. Details:\n"; rc = CURLE_SSL_CACERT; break; default: rc = CURLE_SSL_CONNECT_ERROR; break; } /* detail is already set to the SSL error above */ /* If we e.g. use SSLv2 request-method and the server doesn't like us * (RST connection etc.), OpenSSL gives no explanation whatsoever and * the SO_ERROR is also lost. */ if (CURLE_SSL_CONNECT_ERROR == rc && errdetail == 0) { failf(data, "Unknown SSL protocol error in connection to %s:%d ", conn->host.name, conn->port); return rc; } /* Could be a CERT problem */ SSL_strerror(errdetail, error_buffer, sizeof(error_buffer)); failf(data, "%s%s", cert_problem ? cert_problem : "", error_buffer); return rc; } } else /* we have been connected fine, get out of the connect loop */ break; while(1) { what = Curl_select(readfd, writefd, (int)timeout_ms); if(what > 0) /* reabable or writable, go loop in the outer loop */ break; else if(0 == what) { /* timeout */ failf(data, "SSL connection timeout"); return CURLE_OPERATION_TIMEDOUT; } else { /* anything that gets here is fatally bad */ failf(data, "select on SSL socket, errno: %d", Curl_ourerrno()); return CURLE_SSL_CONNECT_ERROR; } } /* while()-loop for the select() */ } /* while()-loop for the SSL_connect() */ /* Informational message */ infof (data, "SSL connection using %s\n", SSL_get_cipher(connssl->handle)); if(!ssl_sessionid) { /* Since this is not a cached session ID, then we want to stach this one in the cache! */ SSL_SESSION *ssl_sessionid; #ifdef HAVE_SSL_GET1_SESSION ssl_sessionid = SSL_get1_session(connssl->handle); /* SSL_get1_session() will increment the reference count and the session will stay in memory until explicitly freed with SSL_SESSION_free(3), regardless of its state. This function was introduced in openssl 0.9.5a. */ #else ssl_sessionid = SSL_get_session(connssl->handle); /* if SSL_get1_session() is unavailable, use SSL_get_session(). This is an inferior option because the session can be flushed at any time by openssl. It is included only so curl compiles under versions of openssl < 0.9.5a. WARNING: How curl behaves if it's session is flushed is untested. */ #endif retcode = Curl_ssl_addsessionid(conn, ssl_sessionid, 0 /* unknown size */); if(retcode) { failf(data, "failed to store ssl session"); return retcode; } } /* Get server's certificate (note: beware of dynamic allocation) - opt */ /* major serious hack alert -- we should check certificates * to authenticate the server; otherwise we risk man-in-the-middle * attack */ connssl->server_cert = SSL_get_peer_certificate(connssl->handle); if(!connssl->server_cert) { failf(data, "SSL: couldn't get peer certificate!"); return CURLE_SSL_PEER_CERTIFICATE; } infof (data, "Server certificate:\n"); str = X509_NAME_oneline(X509_get_subject_name(connssl->server_cert), NULL, 0); if(!str) { failf(data, "SSL: couldn't get X509-subject!"); X509_free(connssl->server_cert); connssl->server_cert = NULL; return CURLE_SSL_CONNECT_ERROR; } infof(data, "\t subject: %s\n", str); CRYPTO_free(str); certdate = X509_get_notBefore(connssl->server_cert); Curl_ASN1_UTCTIME_output(conn, "\t start date: ", certdate); certdate = X509_get_notAfter(connssl->server_cert); Curl_ASN1_UTCTIME_output(conn, "\t expire date: ", certdate); if(data->set.ssl.verifyhost) { retcode = verifyhost(conn, connssl->server_cert); if(retcode) { X509_free(connssl->server_cert); connssl->server_cert = NULL; return retcode; } } str = X509_NAME_oneline(X509_get_issuer_name(connssl->server_cert), NULL, 0); if(!str) { failf(data, "SSL: couldn't get X509-issuer name!"); retcode = CURLE_SSL_CONNECT_ERROR; } else { infof(data, "\t issuer: %s\n", str); CRYPTO_free(str); /* We could do all sorts of certificate verification stuff here before deallocating the certificate. */ lerr = data->set.ssl.certverifyresult= SSL_get_verify_result(connssl->handle); if(data->set.ssl.certverifyresult != X509_V_OK) { if(data->set.ssl.verifypeer) { /* We probably never reach this, because SSL_connect() will fail and we return earlyer if verifypeer is set? */ failf(data, "SSL certificate verify result: %s (%ld)", X509_verify_cert_error_string(lerr), lerr); retcode = CURLE_SSL_PEER_CERTIFICATE; } else infof(data, "SSL certificate verify result: %s (%ld)," " continuing anyway.\n", X509_verify_cert_error_string(lerr), lerr); } else infof(data, "SSL certificate verify ok.\n"); } X509_free(connssl->server_cert); connssl->server_cert = NULL; return retcode; }
/* * This function is called after the TCP connect has completed. Setup the TLS * layer and do all necessary magic. */ CURLcode Curl_gtls_connect(struct connectdata *conn, int sockindex) { const int cert_type_priority[] = { GNUTLS_CRT_X509, 0 }; struct SessionHandle *data = conn->data; gnutls_session session; int rc; unsigned int cert_list_size; const gnutls_datum *chainp; unsigned int verify_status; gnutls_x509_crt x509_cert; char certbuf[256]; /* big enough? */ size_t size; unsigned int algo; unsigned int bits; time_t clock; const char *ptr; void *ssl_sessionid; size_t ssl_idsize; /* GnuTLS only supports TLSv1 (and SSLv3?) */ if(data->set.ssl.version == CURL_SSLVERSION_SSLv2) { failf(data, "GnuTLS does not support SSLv2"); return CURLE_SSL_CONNECT_ERROR; } /* allocate a cred struct */ rc = gnutls_certificate_allocate_credentials(&conn->ssl[sockindex].cred); if(rc < 0) { failf(data, "gnutls_cert_all_cred() failed: %s", gnutls_strerror(rc)); return CURLE_SSL_CONNECT_ERROR; } if(data->set.ssl.CAfile) { /* set the trusted CA cert bundle file */ rc = gnutls_certificate_set_x509_trust_file(conn->ssl[sockindex].cred, data->set.ssl.CAfile, GNUTLS_X509_FMT_PEM); if(rc < 0) { infof(data, "error reading ca cert file %s (%s)\n", data->set.ssl.CAfile, gnutls_strerror(rc)); } } /* Initialize TLS session as a client */ rc = gnutls_init(&conn->ssl[sockindex].session, GNUTLS_CLIENT); if(rc) { failf(data, "gnutls_init() failed: %d", rc); return CURLE_SSL_CONNECT_ERROR; } /* convenient assign */ session = conn->ssl[sockindex].session; /* Use default priorities */ rc = gnutls_set_default_priority(session); if(rc < 0) return CURLE_SSL_CONNECT_ERROR; /* Sets the priority on the certificate types supported by gnutls. Priority is higher for types specified before others. After specifying the types you want, you must append a 0. */ rc = gnutls_certificate_type_set_priority(session, cert_type_priority); if(rc < 0) return CURLE_SSL_CONNECT_ERROR; /* put the anonymous credentials to the current session */ rc = gnutls_credentials_set(session, GNUTLS_CRD_CERTIFICATE, conn->ssl[sockindex].cred); /* set the connection handle (file descriptor for the socket) */ gnutls_transport_set_ptr(session, (gnutls_transport_ptr)conn->sock[sockindex]); /* This might be a reconnect, so we check for a session ID in the cache to speed up things */ if(!Curl_ssl_getsessionid(conn, &ssl_sessionid, &ssl_idsize)) { /* we got a session id, use it! */ gnutls_session_set_data(session, ssl_sessionid, ssl_idsize); /* Informational message */ infof (data, "SSL re-using session ID\n"); } do { rc = gnutls_handshake(session); if((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED)) { long timeout_ms; long has_passed; if(data->set.timeout || data->set.connecttimeout) { /* get the most strict timeout of the ones converted to milliseconds */ if(data->set.timeout && (data->set.timeout>data->set.connecttimeout)) timeout_ms = data->set.timeout*1000; else timeout_ms = data->set.connecttimeout*1000; } else timeout_ms = DEFAULT_CONNECT_TIMEOUT; /* Evaluate in milliseconds how much time that has passed */ has_passed = Curl_tvdiff(Curl_tvnow(), data->progress.t_startsingle); /* subtract the passed time */ timeout_ms -= has_passed; if(timeout_ms < 0) { /* a precaution, no need to continue if time already is up */ failf(data, "SSL connection timeout"); return CURLE_OPERATION_TIMEOUTED; } rc = Curl_select(conn->sock[sockindex], conn->sock[sockindex], (int)timeout_ms); if(rc > 0) /* reabable or writable, go loop*/ continue; else if(0 == rc) { /* timeout */ failf(data, "SSL connection timeout"); return CURLE_OPERATION_TIMEDOUT; } else { /* anything that gets here is fatally bad */ failf(data, "select on SSL socket, errno: %d", Curl_ourerrno()); return CURLE_SSL_CONNECT_ERROR; } } else break; } while(1); if (rc < 0) { failf(data, "gnutls_handshake() failed: %d", rc); /* gnutls_perror(ret); */ return CURLE_SSL_CONNECT_ERROR; } /* This function will return the peer's raw certificate (chain) as sent by the peer. These certificates are in raw format (DER encoded for X.509). In case of a X.509 then a certificate list may be present. The first certificate in the list is the peer's certificate, following the issuer's certificate, then the issuer's issuer etc. */ chainp = gnutls_certificate_get_peers(session, &cert_list_size); if(!chainp) { if(data->set.ssl.verifyhost) { failf(data, "failed to get server cert"); return CURLE_SSL_PEER_CERTIFICATE; } infof(data, "\t common name: WARNING couldn't obtain\n"); } /* This function will try to verify the peer's certificate and return its status (trusted, invalid etc.). The value of status should be one or more of the gnutls_certificate_status_t enumerated elements bitwise or'd. To avoid denial of service attacks some default upper limits regarding the certificate key size and chain size are set. To override them use gnutls_certificate_set_verify_limits(). */ rc = gnutls_certificate_verify_peers2(session, &verify_status); if (rc < 0) { failf(data, "server cert verify failed: %d", rc); return CURLE_SSL_CONNECT_ERROR; } /* verify_status is a bitmask of gnutls_certificate_status bits */ if(verify_status & GNUTLS_CERT_INVALID) { if (data->set.ssl.verifypeer) { failf(data, "server certificate verification failed. CAfile: %s", data->set.ssl.CAfile?data->set.ssl.CAfile:"none"); return CURLE_SSL_CACERT; } else infof(data, "\t server certificate verification FAILED\n"); } else infof(data, "\t server certificate verification OK\n"); /* initialize an X.509 certificate structure. */ gnutls_x509_crt_init(&x509_cert); /* convert the given DER or PEM encoded Certificate to the native gnutls_x509_crt_t format */ gnutls_x509_crt_import(x509_cert, chainp, GNUTLS_X509_FMT_DER); size=sizeof(certbuf); rc = gnutls_x509_crt_get_dn_by_oid(x509_cert, GNUTLS_OID_X520_COMMON_NAME, 0, /* the first and only one */ FALSE, certbuf, &size); if(rc) { infof(data, "error fetching CN from cert:%s\n", gnutls_strerror(rc)); } /* This function will check if the given certificate's subject matches the given hostname. This is a basic implementation of the matching described in RFC2818 (HTTPS), which takes into account wildcards, and the subject alternative name PKIX extension. Returns non zero on success, and zero on failure. */ rc = gnutls_x509_crt_check_hostname(x509_cert, conn->host.name); if(!rc) { if (data->set.ssl.verifyhost > 1) { failf(data, "SSL: certificate subject name (%s) does not match " "target host name '%s'", certbuf, conn->host.dispname); gnutls_x509_crt_deinit(x509_cert); return CURLE_SSL_PEER_CERTIFICATE; } else infof(data, "\t common name: %s (does not match '%s')\n", certbuf, conn->host.dispname); } else infof(data, "\t common name: %s (matched)\n", certbuf); /* Show: - ciphers used - subject - start date - expire date - common name - issuer */ /* public key algorithm's parameters */ algo = gnutls_x509_crt_get_pk_algorithm(x509_cert, &bits); infof(data, "\t certificate public key: %s\n", gnutls_pk_algorithm_get_name(algo)); /* version of the X.509 certificate. */ infof(data, "\t certificate version: #%d\n", gnutls_x509_crt_get_version(x509_cert)); size = sizeof(certbuf); gnutls_x509_crt_get_dn(x509_cert, certbuf, &size); infof(data, "\t subject: %s\n", certbuf); clock = gnutls_x509_crt_get_activation_time(x509_cert); showtime(data, "start date", clock); clock = gnutls_x509_crt_get_expiration_time(x509_cert); showtime(data, "expire date", clock); size = sizeof(certbuf); gnutls_x509_crt_get_issuer_dn(x509_cert, certbuf, &size); infof(data, "\t issuer: %s\n", certbuf); gnutls_x509_crt_deinit(x509_cert); /* compression algorithm (if any) */ ptr = gnutls_compression_get_name(gnutls_compression_get(session)); /* the *_get_name() says "NULL" if GNUTLS_COMP_NULL is returned */ infof(data, "\t compression: %s\n", ptr); /* the name of the cipher used. ie 3DES. */ ptr = gnutls_cipher_get_name(gnutls_cipher_get(session)); infof(data, "\t cipher: %s\n", ptr); /* the MAC algorithms name. ie SHA1 */ ptr = gnutls_mac_get_name(gnutls_mac_get(session)); infof(data, "\t MAC: %s\n", ptr); if(!ssl_sessionid) { /* this session was not previously in the cache, add it now */ /* get the session ID data size */ gnutls_session_get_data(session, NULL, &ssl_idsize); ssl_sessionid = malloc(ssl_idsize); /* get a buffer for it */ if(ssl_sessionid) { /* extract session ID to the allocated buffer */ gnutls_session_get_data(session, ssl_sessionid, &ssl_idsize); /* store this session id */ return Curl_ssl_addsessionid(conn, ssl_sessionid, ssl_idsize); } } return CURLE_OK; }