summaryrefslogblamecommitdiffstats
path: root/client.c
blob: e9e945379ed1a34124c4f11d4aaecb10bb037b14 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12




                   
                    





                   
                              
 

                                                   




                                                                 
                                                       









                                                                                                                                                                              
                                        



                                         






































                                                                                                                               



                                                                       
                                                    

                                       








                                                                                                
                         
                 























                                                                                                                            
         

 
                                                  
 
                                     

                                                                  
























                                                                                                        

                                     





                                                                                                                                         

                                           


                                                                                            

 
                                                                                         
 
                                                                  



                                                                                 

                                            
                 
                                                 





                                                                                

                                                                        




                                                                                                   




                                                                                                                        





                                                                                                                    
                                                                                                        




                                                                                                                                                                                          




                                                                 

                                          

 
#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>

#define MAX_SEND_BUFFER 150000

static void client_haveIn(epoll_client_t *client);
static void client_haveOut(epoll_client_t *client);

static 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.\n");
		client_free(client);
		return;
	}
	if (client->ssl == NULL) {
		// Plain connection
		if (haveIn) client_haveIn(client);
		if (haveOut) client_haveOut(client);
		if (client->kill) client_free(client);
		return;
	}
	// SSL connection
	if (!client->sslAccepted) {
		// Still SSL-Connecting
		if (!ssl_acceptClient(client)) {
			printf("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);
	client_haveOut(client);
	if (client->kill) {
		printf("Client killed.\n");
		client_free(client);
	}
}

static void client_haveIn(epoll_client_t *client)
{
	for (;;) {
		if (client->rbPos >= REQLEN) {
			printf("[C->Proxy] Read buffer overflow. 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) {
				printf("Client gone while reading.\n");
				client->kill = TRUE;
				return;
			}
		} else {
			// SSL
			ret = SSL_read(client->ssl, client->readBuffer + client->rbPos, buflen);
			if (ret <= 0) {
				int err = SSL_get_error(client->ssl, ret);
				if (SSL_BLOCKED(err)) break;
				printf("Client gone while reading (%d, %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
			printf("Received complete requrest...\n");
			if (!proxy_fromClient(client, len)) {
				printf("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
	}
}

static void client_haveOut(epoll_client_t *client)
{
	client->writeBlocked = 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;
			if (ret <= 0) {
				printf("Cannot send to client (ret: %d, errno: %d)\n", (int)ret, errno);
				client->kill = TRUE;
				return;
			}
		} 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; // Blocking
				}
				printf("SSL client gone while sending (%d)\n", err);
				ERR_print_errors_fp(stdout);
				client->kill = TRUE;
				return; // 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; // Sent less than requested -> buffer full, epoll will trigger us again
	}
	client->sbPos = client->sbFill = 0;
	if (client->ssl == NULL && client->sbLen > MAX_SEND_BUFFER / 2) {
		helper_realloc(&client->sendBuffer, &client->sbLen, 8000, "client_haveOut");
	}
}

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("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("SSL Write blocked and buffer to small (%d)\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 > MAX_SEND_BUFFER) {
			printf("Dropping client as the send buffer would exceed %d bytes.\n", (int)MAX_SEND_BUFFER);
			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 ? MAX_SEND_BUFFER : 4000), "client_send") == -1) {
				client->kill = TRUE;
				return FALSE;
			}
			printf("Send Buffer now %d\n", (int)client->sbLen);
		}
	}
	// Finally append to buffer
	memcpy(client->sendBuffer + client->sbFill, buffer, len);
	client->sbFill += len;
	if (!cork) client_haveOut(client);
	return TRUE;
}