/*
 *	Write data to a secure connection.
 */
ssize_t
be_tls_write(Port *port, void *ptr, size_t len, int *waitfor)
{
	ssize_t		n;
	int			err;

	/*
	 * If SSL renegotiations are enabled and we're getting close to the
	 * limit, start one now; but avoid it if there's one already in
	 * progress.  Request the renegotiation 1kB before the limit has
	 * actually expired.
	 */
	if (ssl_renegotiation_limit && !in_ssl_renegotiation &&
		port->count > (ssl_renegotiation_limit - 1) * 1024L)
	{
		in_ssl_renegotiation = true;

		/*
		 * The way we determine that a renegotiation has completed is by
		 * observing OpenSSL's internal renegotiation counter.  Make sure
		 * we start out at zero, and assume that the renegotiation is
		 * complete when the counter advances.
		 *
		 * OpenSSL provides SSL_renegotiation_pending(), but this doesn't
		 * seem to work in testing.
		 */
		SSL_clear_num_renegotiations(port->ssl);

		/* without this, renegotiation fails when a client cert is used */
		SSL_set_session_id_context(port->ssl, (void *) &SSL_context,
								   sizeof(SSL_context));

		if (SSL_renegotiate(port->ssl) <= 0)
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL failure during renegotiation start")));
	}

	errno = 0;
	n = SSL_write(port->ssl, ptr, len);
	err = SSL_get_error(port->ssl, n);
	switch (err)
	{
		case SSL_ERROR_NONE:
			port->count += n;
			break;
		case SSL_ERROR_WANT_READ:
			*waitfor = WL_SOCKET_READABLE;
			errno = EWOULDBLOCK;
			n = -1;
			break;
		case SSL_ERROR_WANT_WRITE:
			*waitfor = WL_SOCKET_WRITEABLE;
			errno = EWOULDBLOCK;
			n = -1;
			break;
		case SSL_ERROR_SYSCALL:
			/* leave it to caller to ereport the value of errno */
			if (n != -1)
			{
				errno = ECONNRESET;
				n = -1;
			}
			break;
		case SSL_ERROR_SSL:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL error: %s", SSLerrmessage())));
			/* fall through */
		case SSL_ERROR_ZERO_RETURN:
			errno = ECONNRESET;
			n = -1;
			break;
		default:
			ereport(COMMERROR,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("unrecognized SSL error code: %d",
							err)));
			errno = ECONNRESET;
			n = -1;
			break;
	}

	if (n >= 0)
	{
		/* is renegotiation complete? */
		if (in_ssl_renegotiation &&
			SSL_num_renegotiations(port->ssl) >= 1)
		{
			in_ssl_renegotiation = false;
			port->count = 0;
		}

		/*
		 * if renegotiation is still ongoing, and we've gone beyond the
		 * limit, kill the connection now -- continuing to use it can be
		 * considered a security problem.
		 */
		if (in_ssl_renegotiation &&
			port->count > ssl_renegotiation_limit * 1024L)
			ereport(FATAL,
					(errcode(ERRCODE_PROTOCOL_VIOLATION),
					 errmsg("SSL failed to renegotiate connection before limit expired")));
	}

	return n;
}
Esempio n. 2
0
/*
 *	Write data to a secure connection.
 */
ssize_t
secure_write(Port *port, void *ptr, size_t len)
{
	ssize_t		n;

#ifdef USE_SSL
	if (port->ssl)
	{
		int			err;

		/*
		 * If SSL renegotiations are enabled and we're getting close to the
		 * limit, start one now; but avoid it if there's one already in
		 * progress.  Request the renegotiation 1kB before the limit has
		 * actually expired.
		 */
		if (ssl_renegotiation_limit && !in_ssl_renegotiation &&
			port->count > (ssl_renegotiation_limit - 1) * 1024L)
		{
			in_ssl_renegotiation = true;

			/*
			 * The way we determine that a renegotiation has completed is by
			 * observing OpenSSL's internal renegotiation counter.  Make sure
			 * we start out at zero, and assume that the renegotiation is
			 * complete when the counter advances.
			 *
			 * OpenSSL provides SSL_renegotiation_pending(), but this doesn't
			 * seem to work in testing.
			 */
			SSL_clear_num_renegotiations(port->ssl);

			SSL_set_session_id_context(port->ssl, (void *) &SSL_context,
									   sizeof(SSL_context));
			if (SSL_renegotiate(port->ssl) <= 0)
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("SSL failure during renegotiation start")));
			else
			{
				int			retries;

				/*
				 * A handshake can fail, so be prepared to retry it, but only
				 * a few times.
				 */
				for (retries = 0; retries++;)
				{
					if (SSL_do_handshake(port->ssl) > 0)
						break;	/* done */
					ereport(COMMERROR,
							(errcode(ERRCODE_PROTOCOL_VIOLATION),
							 errmsg("SSL handshake failure on renegotiation, retrying")));
					if (retries >= 20)
						ereport(FATAL,
								(errcode(ERRCODE_PROTOCOL_VIOLATION),
								 errmsg("unable to complete SSL handshake")));
				}
			}
		}

wloop:
		errno = 0;
		n = SSL_write(port->ssl, ptr, len);
		err = SSL_get_error(port->ssl, n);
		switch (err)
		{
			case SSL_ERROR_NONE:
				port->count += n;
				break;
			case SSL_ERROR_WANT_READ:
			case SSL_ERROR_WANT_WRITE:
#ifdef WIN32
				pgwin32_waitforsinglesocket(SSL_get_fd(port->ssl),
											(err == SSL_ERROR_WANT_READ) ?
									FD_READ | FD_CLOSE : FD_WRITE | FD_CLOSE,
											INFINITE);
#endif
				goto wloop;
			case SSL_ERROR_SYSCALL:
				/* leave it to caller to ereport the value of errno */
				if (n != -1)
				{
					errno = ECONNRESET;
					n = -1;
				}
				break;
			case SSL_ERROR_SSL:
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("SSL error: %s", SSLerrmessage())));
				/* fall through */
			case SSL_ERROR_ZERO_RETURN:
				errno = ECONNRESET;
				n = -1;
				break;
			default:
				ereport(COMMERROR,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("unrecognized SSL error code: %d",
								err)));
				errno = ECONNRESET;
				n = -1;
				break;
		}

		if (n >= 0)
		{
			/* is renegotiation complete? */
			if (in_ssl_renegotiation &&
				SSL_num_renegotiations(port->ssl) >= 1)
			{
				in_ssl_renegotiation = false;
				port->count = 0;
			}

			/*
			 * if renegotiation is still ongoing, and we've gone beyond the
			 * limit, kill the connection now -- continuing to use it can be
			 * considered a security problem.
			 */
			if (in_ssl_renegotiation &&
				port->count > ssl_renegotiation_limit * 1024L)
				ereport(FATAL,
						(errcode(ERRCODE_PROTOCOL_VIOLATION),
						 errmsg("SSL failed to renegotiate connection before limit expired")));
		}
	}
	else
#endif
		n = send(port->sock, ptr, len, 0);

	return n;
}