#include "client.h"
#include "proxy.h"
#include "epoll.h"
#include "helper.h"
#include "asn1.h"
#include "openssl.h"
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
static void client_haveIn(epoll_client_t *client);
static BOOL client_haveOut(epoll_client_t *client);
void client_free(epoll_client_t *client)
{
proxy_removeClient(client);
if (client->sendBuffer != NULL) free(client->sendBuffer);
if (client->ssl != NULL) SSL_free(client->ssl);
if (client->fd != -1) {
//ePoll_remove(client->fd); // epoll would remove closed descriptors automatically, but just to be sure we don't cause use after free, we remove it explicitly
close(client->fd);
}
free(client);
}
void client_callback(void *data, int haveIn, int haveOut, int doCleanup)
{
epoll_client_t *client = (epoll_client_t*)data;
if (doCleanup || client->kill) {
//printf("Client gone (1).\n");
client_free(client);
return;
}
if (client->ssl == NULL) {
// Plain connection
if (haveIn) client_haveIn(client);
if (haveOut || client->kill) {
if (client_haveOut(client)) return;
}
if (client->kill) {
//printf("Client gone (2).\n");
client_free(client);
}
return;
}
// SSL connection
if (!client->sslAccepted) {
// Still SSL-Connecting
if (!ssl_acceptClient(client)) {
printf("[Proxy] SSL Client accept failed.\n");
client_free(client);
return;
}
if (!client->sslAccepted) return;
}
// Since we don't know if the incoming data is just wrapped application data or ssl protocol stuff, we always call both
client_haveIn(client);
if (client_haveOut(client)) return;
if (client->kill) {
//printf("Client gone (3).\n");
client_free(client);
}
}
static void client_haveIn(epoll_client_t *client)
{
for (;;) {
if (client->rbPos >= REQLEN) {
printf("[Proxy] Buffer overflow reading from client. Disconnecting.\n");
client->kill = TRUE;
return;
}
const size_t buflen = REQLEN - client->rbPos;
ssize_t ret;
if (client->ssl == NULL) {
// Plain
ret = read(client->fd, client->readBuffer + client->rbPos, buflen);
if (ret < 0 && errno == EINTR) continue;
if (ret < 0 && errno == EAGAIN) break;
if (ret <= 0) {
if (client->kill) return;
printf("[Proxy] Client gone while reading (ret=%d, errno=%d).\n", (int)ret, errno);
client->kill = TRUE;
return;
}
} else {
// SSL
ret = SSL_read(client->ssl, client->readBuffer + client->rbPos, buflen);
if (ret <= 0) {
if (client->kill) return;
int err = SSL_get_error(client->ssl, ret);
if (SSL_BLOCKED(err)) break;
if (err == SSL_ERROR_ZERO_RETURN) {
SSL_shutdown(client->ssl);
// Might have to handle error return codes; need another state in epoll_client_t for this
}
printf("[Proxy] SSL client gone while reading (ret=%d, err=%d).\n", (int)ret, err);
client->kill = TRUE;
return;
}
}
// We reach here if something was read (so ret > 0)
client->rbPos += ret;
// Request complete?
for (;;) {
size_t consumed, len;
consumed = scan_asn1SEQUENCE(client->readBuffer, client->readBuffer + client->rbPos, &len);
if (consumed == 0) break; // Length-Header not complete
len += consumed;
if (len > client->rbPos) break; // Body not complete yet
if (!proxy_fromClient(client, len)) {
printf("[Proxy] Error parsing request from client.\n");
client->kill = TRUE;
return;
}
// Shift remaining buffer contents
if (len == client->rbPos) {
client->rbPos = 0;
break;
}
memmove(client->readBuffer, client->readBuffer + len, client->rbPos - len);
client->rbPos -= len;
}
if (client->ssl == NULL && (ssize_t)buflen > ret) break; // Read less than buffer len, epoll will fire again
}
}
/**
* Send any queued data.
* Returns TRUE if anything has been sent, or the socket
* buffer is currently full (EAGAIN, non-blocking mode)
*/
static BOOL client_haveOut(epoll_client_t *client)
{
client->writeBlocked = FALSE;
if (client->sbPos >= client->sbFill) return FALSE;
while (client->sbPos < client->sbFill) {
const int tosend = client->sbFill - client->sbPos;
ssize_t ret;
if (client->ssl == NULL) {
// Plain
ret = write(client->fd, client->sendBuffer + client->sbPos, tosend);
if (ret < 0 && errno == EINTR) continue;
if (ret < 0 && errno == EAGAIN) return TRUE;
if (ret <= 0) {
printf("[Proxy] Cannot send to client (ret=%d, errno=%d)\n", (int)ret, errno);
client->kill = TRUE;
return FALSE;
}
} else {
// SSL
ret = SSL_write(client->ssl, client->sendBuffer + client->sbPos, tosend);
if (ret <= 0) {
int err = SSL_get_error(client->ssl, ret);
if (SSL_BLOCKED(err)) {
client->writeBlocked = TRUE;
return TRUE; // Blocking
}
printf("[Proxy] SSL cannot send to client (ret=%d, err=%d)\n", (int)ret, err);
ERR_print_errors_fp(stdout);
client->kill = TRUE;
return FALSE; // Closed
}
}
client->sbPos += ret;
if (client->ssl != NULL) {
memmove(client->sendBuffer, client->sendBuffer + client->sbPos, client->sbFill - client->sbPos);
client->sbFill -= client->sbPos;
client->sbPos = 0;
}
if (client->ssl == NULL && ret != tosend) return TRUE; // Sent less than requested -> buffer full, epoll will trigger us again
}
client->sbPos = client->sbFill = 0;
if (client->ssl == NULL && client->sbLen > MAXMSGLEN / 2) {
helper_realloc(&client->sendBuffer, &client->sbLen, 8000, "client_haveOut");
}
return TRUE;
}
BOOL client_send(epoll_client_t *client, const char *buffer, size_t len, const BOOL cork)
{
if (client->ssl == NULL && client->sbFill == 0 && !cork) {
// Nothing in send buffer, fire away
const int ret = write(client->fd, buffer, len);
if (ret == 0 || (ret < 0 && errno != EINTR && errno != EAGAIN)) {
printf("[Proxy] Client gone when trying to send.\n");
client->kill = TRUE;
return FALSE;
}
if (ret == (int)len) return TRUE;
// Couldn't send everything, continue with buffering logic below
if (ret > 0) {
buffer += ret;
len -= (size_t)ret;
}
}
// Buffer...
if (client->sbLen - client->sbFill < len) { // Buffer too small?
if (client->writeBlocked) {
printf("[Proxy] SSL write to client blocked and buffer to small (%d bytes)\n", (int)client->sbLen);
client->kill = TRUE;
return FALSE;
}
if (client->sbPos != 0) { // See if we can compact it
memmove(client->sendBuffer, client->sendBuffer + client->sbPos, client->sbFill - client->sbPos);
client->sbFill -= client->sbPos;
client->sbPos = 0;
}
// Sanity
if (client->sbFill + len > MAXMSGLEN) {
printf("[Proxy] Dropping client as the send buffer would exceed %d bytes.\n", (int)MAXMSGLEN);
client->kill = TRUE;
return FALSE;
}
if (client->sbLen - client->sbFill < len) { // Still too small after compacting? realloc
if (helper_realloc(&client->sendBuffer, &client->sbLen, client->sbLen + len + (client->ssl && client->sbLen > 0 ? MAXMSGLEN : 4000), "client_send") == -1) {
client->kill = TRUE;
return FALSE;
}
}
}
// Finally append to buffer
memcpy(client->sendBuffer + client->sbFill, buffer, len);
client->sbFill += len;
if (!cork) client_haveOut(client);
return TRUE;
}
BOOL client_searchResultError(epoll_client_t *client, const unsigned long messageId, const int code, const char *message)
{
const size_t doneLen = fmt_ldapsearchresultdone(NULL, code, "", message, "");
const size_t doneHeaderLen = fmt_ldapmessage(NULL, messageId, SearchResultDone, doneLen);
char buffer[doneLen + doneHeaderLen];
fmt_ldapsearchresultdone(buffer + doneHeaderLen, code, "", message, "");
fmt_ldapmessage(buffer, messageId, SearchResultDone, doneLen);
return client_send(client, buffer, doneHeaderLen + doneLen, FALSE);
}