diff --git a/lib/urldata.h b/lib/urldata.h index 01a20ae19..7d6896404 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -331,6 +331,8 @@ struct ssl_connect_data { gsk_handle handle; int iocport; ssl_connect_state connecting_state; + int localfd; + int remotefd; #endif #ifdef USE_AXTLS SSL_CTX* ssl_ctx; diff --git a/lib/vtls/gskit.c b/lib/vtls/gskit.c index 78303aacd..d1494966d 100644 --- a/lib/vtls/gskit.c +++ b/lib/vtls/gskit.c @@ -81,6 +81,10 @@ #include "memdebug.h" +/* Directions. */ +#define SOS_READ 0x01 +#define SOS_WRITE 0x02 + /* SSL version flags. */ #define CURL_GSKPROTO_SSLV2 0 #define CURL_GSKPROTO_SSLV2_MASK (1 << CURL_GSKPROTO_SSLV2) @@ -501,6 +505,148 @@ static void close_async_handshake(struct ssl_connect_data *connssl) connssl->iocport = -1; } +/* SSL over SSL + * Problems: + * 1) GSKit can only perform SSL on an AF_INET or AF_INET6 stream socket. To + * pipe an SSL stream into another, it is therefore needed to have a pair + * of such communicating sockets and handle the pipelining explicitly. + * 2) OS/400 socketpair() is only implemented for domain AF_UNIX, thus cannot + * be used to produce the pipeline. + * The solution is to simulate socketpair() for AF_INET with low-level API + * listen(), bind() and connect(). + */ + +static int +inetsocketpair(int sv[2]) +{ + int lfd; /* Listening socket. */ + int sfd; /* Server socket. */ + int cfd; /* Client socket. */ + int len; + struct sockaddr_in laddr; + + /* Create listening socket on a local dynamic port. */ + lfd = socket(AF_INET, SOCK_STREAM, 0); + if(lfd < 0) + return -1; + memset((char *) &laddr, 0, sizeof laddr); + laddr.sin_family = AF_INET; + laddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + laddr.sin_port = 0; + if(bind(lfd, (struct sockaddr *) &laddr, sizeof laddr) || + listen(lfd, 0) < 0) { + close(lfd); + return -1; + } + + /* Get the allocated port. */ + len = sizeof laddr; + if(getsockname(lfd, (struct sockaddr *) &laddr, &len) < 0) { + close(lfd); + return -1; + } + + /* Create the client socket. */ + cfd = socket(AF_INET, SOCK_STREAM, 0); + if(cfd < 0) { + close(lfd); + return -1; + } + + /* Request unblocking connection to the listening socket. */ + curlx_nonblock(cfd, TRUE); + if(connect(cfd, (struct sockaddr *) &laddr, sizeof laddr) < 0 && + errno != EINPROGRESS) { + close(lfd); + close(cfd); + return -1; + } + + /* Accept the incoming connection and get the server socket. */ + sfd = accept(lfd, NULL, NULL); + close(lfd); + if(sfd < 0) { + close(cfd); + return -1; + } + + /* Done, return sockets and succeed. */ + curlx_nonblock(cfd, FALSE); + sv[0] = cfd; + sv[1] = sfd; + return 0; +} + +static int pipe_ssloverssl(struct connectdata *conn, int sockindex, + int directions) +{ + struct ssl_connect_data *connssl = &conn->ssl[sockindex]; + struct ssl_connect_data *connproxyssl = &conn->proxy_ssl[sockindex]; + fd_set fds_read; + fd_set fds_write; + int n; + int m; + int i; + struct timeval tv = {0, 0}; + char buf[CURL_MAX_WRITE_SIZE]; + + if(!connssl->use || !connproxyssl->use) + return 0; /* No SSL over SSL: OK. */ + + FD_ZERO(&fds_read); + FD_ZERO(&fds_write); + n = -1; + if(directions & SOS_READ) { + FD_SET(connssl->remotefd, &fds_write); + n = connssl->remotefd; + } + if(directions & SOS_WRITE) { + FD_SET(connssl->remotefd, &fds_read); + n = connssl->remotefd; + FD_SET(conn->sock[sockindex], &fds_write); + if(n < conn->sock[sockindex]) + n = conn->sock[sockindex]; + } + i = select(n + 1, &fds_read, &fds_write, NULL, &tv); + if(i < 0) + return -1; /* Select error. */ + + if(FD_ISSET(connssl->remotefd, &fds_write)) { + /* Try getting data from HTTPS proxy and pipe it upstream. */ + n = 0; + i = gsk_secure_soc_read(connproxyssl->handle, buf, sizeof buf, &n); + switch(i) { + case GSK_OK: + if(n) { + i = write(connssl->remotefd, buf, n); + if(i < 0) + return -1; + } + break; + case GSK_INVALID_STATE: + case GSK_WOULD_BLOCK: + break; + default: + return -1; + } + } + + if(FD_ISSET(connssl->remotefd, &fds_read) && + FD_ISSET(conn->sock[sockindex], &fds_write)) { + /* Pipe data to HTTPS proxy. */ + n = read(connssl->remotefd, buf, sizeof buf); + if(n < 0) + return -1; + if(n) { + i = gsk_secure_soc_write(connproxyssl->handle, buf, n, &m); + if(i != GSK_OK || n != m) + return -1; + } + } + + return 0; /* OK */ +} + static void close_one(struct ssl_connect_data *connssl, struct SessionHandle *data) @@ -509,6 +655,14 @@ static void close_one(struct ssl_connect_data *connssl, gskit_status(data, gsk_secure_soc_close(&connssl->handle), "gsk_secure_soc_close()", 0); connssl->handle = (gsk_handle) NULL; + if(connssl->localfd >= 0) { + close(connssl->localfd); + connssl->localfd = -1; + } + if(connssl->remotefd >= 0) { + close(connssl->remotefd); + connssl->remotefd = -1; + } } if(connssl->iocport >= 0) close_async_handshake(connssl); @@ -519,13 +673,18 @@ static ssize_t gskit_send(struct connectdata *conn, int sockindex, const void *mem, size_t len, CURLcode *curlcode) { struct SessionHandle *data = conn->data; - CURLcode cc; + CURLcode cc = CURLE_SEND_ERROR; int written; - cc = gskit_status(data, - gsk_secure_soc_write(conn->ssl[sockindex].handle, - (char *) mem, (int) len, &written), - "gsk_secure_soc_write()", CURLE_SEND_ERROR); + if(!pipe_ssloverssl(conn, sockindex, SOS_WRITE)) { + cc = gskit_status(data, + gsk_secure_soc_write(conn->ssl[sockindex].handle, + (char *) mem, (int) len, &written), + "gsk_secure_soc_write()", CURLE_SEND_ERROR); + if(cc == CURLE_OK) + if(pipe_ssloverssl(conn, sockindex, SOS_WRITE)) + cc = CURLE_SEND_ERROR; + } if(cc != CURLE_OK) { *curlcode = cc; written = -1; @@ -540,12 +699,14 @@ static ssize_t gskit_recv(struct connectdata *conn, int num, char *buf, struct SessionHandle *data = conn->data; int buffsize; int nread; - CURLcode cc; + CURLcode cc = CURLE_RECV_ERROR; - buffsize = buffersize > (size_t) INT_MAX? INT_MAX: (int) buffersize; - cc = gskit_status(data, gsk_secure_soc_read(conn->ssl[num].handle, - buf, buffsize, &nread), - "gsk_secure_soc_read()", CURLE_RECV_ERROR); + if(!pipe_ssloverssl(conn, num, SOS_READ)) { + buffsize = buffersize > (size_t) INT_MAX? INT_MAX: (int) buffersize; + cc = gskit_status(data, gsk_secure_soc_read(conn->ssl[num].handle, + buf, buffsize, &nread), + "gsk_secure_soc_read()", CURLE_RECV_ERROR); + } if(cc != CURLE_OK) { *curlcode = cc; nread = -1; @@ -572,21 +733,15 @@ static CURLcode gskit_connect_step1(struct connectdata *conn, int sockindex) unsigned int protoflags; long timeout; Qso_OverlappedIO_t commarea; - - /* GSKit does not feature an easy way to manipulate the encrypted data, - thus SSL stacking is not yet implemented. - GSKit always reads/writes the encrypted data from/to a file descriptor: - the stacking could therefore be implemented via a socketpair() and - and by periodically calling an interface procedure (no threads, no fork on - OS/400 in interactive mode). */ - - if(conn->proxy_ssl[sockindex].use) - return CURLE_UNSUPPORTED_PROTOCOL; + int sockpair[2]; + static const int sobufsize = CURL_MAX_WRITE_SIZE; /* Create SSL environment, start (preferably asynchronous) handshake. */ connssl->handle = (gsk_handle) NULL; connssl->iocport = -1; + connssl->localfd = -1; + connssl->remotefd = -1; /* GSKit supports two ways of specifying an SSL context: either by * application identifier (that should have been defined at the system @@ -625,6 +780,24 @@ static CURLcode gskit_connect_step1(struct connectdata *conn, int sockindex) if(result) return result; + /* Establish a pipelining socket pair for SSL over SSL. */ + if(conn->proxy_ssl[sockindex].use) { + if(inetsocketpair(sockpair)) + return CURLE_SSL_CONNECT_ERROR; + connssl->localfd = sockpair[0]; + connssl->remotefd = sockpair[1]; + setsockopt(connssl->localfd, SOL_SOCKET, SO_RCVBUF, + (void *) sobufsize, sizeof sobufsize); + setsockopt(connssl->remotefd, SOL_SOCKET, SO_RCVBUF, + (void *) sobufsize, sizeof sobufsize); + setsockopt(connssl->localfd, SOL_SOCKET, SO_SNDBUF, + (void *) sobufsize, sizeof sobufsize); + setsockopt(connssl->remotefd, SOL_SOCKET, SO_SNDBUF, + (void *) sobufsize, sizeof sobufsize); + curlx_nonblock(connssl->localfd, TRUE); + curlx_nonblock(connssl->remotefd, TRUE); + } + /* Determine which SSL/TLS version should be enabled. */ protoflags = CURL_GSKPROTO_TLSV10_MASK | CURL_GSKPROTO_TLSV11_MASK | CURL_GSKPROTO_TLSV12_MASK; @@ -673,7 +846,8 @@ static CURLcode gskit_connect_step1(struct connectdata *conn, int sockindex) (timeout + 999) / 1000); } if(!result) - result = set_numeric(data, connssl->handle, GSK_FD, conn->sock[sockindex]); + result = set_numeric(data, connssl->handle, GSK_FD, connssl->localfd >= 0? + connssl->localfd: conn->sock[sockindex]); if(!result) result = set_ciphers(conn, connssl->handle, &protoflags); if(!protoflags) { @@ -925,6 +1099,11 @@ static CURLcode gskit_connect_common(struct connectdata *conn, int sockindex, result = gskit_connect_step1(conn, sockindex); } + /* Handle handshake pipelining. */ + if(!result) + if(pipe_ssloverssl(conn, sockindex, SOS_READ | SOS_WRITE)) + result = CURLE_SSL_CONNECT_ERROR; + /* Step 2: check if handshake is over. */ if(!result && connssl->connecting_state == ssl_connect_2) { /* check allowed time left */ @@ -939,6 +1118,11 @@ static CURLcode gskit_connect_common(struct connectdata *conn, int sockindex, result = gskit_connect_step2(conn, sockindex, nonblocking); } + /* Handle handshake pipelining. */ + if(!result) + if(pipe_ssloverssl(conn, sockindex, SOS_READ | SOS_WRITE)) + result = CURLE_SSL_CONNECT_ERROR; + /* Step 3: gather certificate info, verify host. */ if(!result && connssl->connecting_state == ssl_connect_3) result = gskit_connect_step3(conn, sockindex); diff --git a/lib/vtls/vtls.c b/lib/vtls/vtls.c index 81b0c116a..fc716dd8e 100644 --- a/lib/vtls/vtls.c +++ b/lib/vtls/vtls.c @@ -308,7 +308,8 @@ ssl_connect_init_proxy(struct connectdata *conn, int sockindex) { DEBUGASSERT(conn->bits.proxy_ssl_connected[sockindex]); if(ssl_connection_complete == conn->ssl[sockindex].state && !conn->proxy_ssl[sockindex].use) { -#if defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_NSS) +#if defined(USE_OPENSSL) || defined(USE_GNUTLS) || defined(USE_NSS) || \ + defined(USE_GSKIT) conn->proxy_ssl[sockindex] = conn->ssl[sockindex]; memset(&conn->ssl[sockindex], 0, sizeof(conn->ssl[sockindex])); #else