/*
	Servidor vulnerável a buffer overflow
	Diego de Freitas Aranha, 00/01805
	Arquivo servidor.c
*/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define BUFFER_SIZE 100// Tamanho do buffer de recebimento
#define BACKLOG 5// Número de conexões na fila do servidor

/* Protótipos de funções */
/* Função de processamento da mensagem recebida */
void process(char *buffer);
/* Função de saída em caso de erro */
void quit_with_error(char * error_message);
/* Rotina para fechamento das conexões e liberação dos sockets */
void cleanup(int socket_descriptor, int incoming_socket);

/* Ponto de entrada do programa */
int main(int argc, char *argv[]) {
	/* Descritor do socket servidor */
	int socket_descriptor = -1;
	/* Buffer de recebimento */
	char buffer[BUFFER_SIZE];
	/* Descritor do socket de conexão com cliente */
	int incoming_socket;
	/* Registro para armazenar endereço do servidor */
	struct sockaddr_in my_address;
	/* Registro para armazenar endereço do cliente */
	struct sockaddr_in their_address;
	/* Porta em que o servidor irá escutar */
	int server_port = 0;
	/* Inteiro para armazenar o número de btyes recebidos a cada chamada de read(2) */
	int message_length;
	/* Flag utilizada para ligar o reuso da porta do servidor */
	int i_want_reusable_ports = 1;
	/* Inteiro utilizado para armazenar o tamanho da estrutura sockaddr */
	int length;
	/* Inteiro utilizado para indexar o buffer de recebimento */
	int index;
	
	/* Checagem de parâmetros do servidor */
	if (argc!=2) {
		fprintf(stderr,"Sinopse: %s <porta>\n", argv[0]);
		exit(1);
	}
	
	/* Obtenção da porta a partir da linha de comando */
	server_port = atoi(argv[1]);
	/* Criação de um socket TCP */
	socket_descriptor = socket(AF_INET, SOCK_STREAM, 0);

	/* Checagem da criação do socket TCP */
	if (socket_descriptor < 0) {
		cleanup(socket_descriptor, incoming_socket);
		quit_with_error("Não foi possível abrir socket TCP.\n");
	}

	/* Ligação do reuso na porta utilizada pelo socket */
	if (setsockopt(socket_descriptor, SOL_SOCKET, SO_REUSEADDR, &i_want_reusable_ports, sizeof(int)) == -1) {
		cleanup(socket_descriptor, incoming_socket);
		quit_with_error("Não foi possível tornar a porta do socket reusável.\n");
	}

	/* Montagem do registro que armazena o endereço da máquina executando o servidor */
	my_address.sin_family = AF_INET;
	my_address.sin_port = htons(server_port);
	my_address.sin_addr.s_addr = INADDR_ANY;
	memset(&(my_address.sin_zero), '0', 8);

	/* Alocação da porta fornecida para o socket servidor */
	if (bind(socket_descriptor, (struct sockaddr *) &my_address, sizeof(my_address)) < 0) {
		cleanup(socket_descriptor, incoming_socket);
		quit_with_error("Não foi possível alocar porta para o socket.\n");
	}

	/* Socket em modo de escuta */
	if (listen(socket_descriptor, BACKLOG) == -1) {
		cleanup(socket_descriptor, incoming_socket);
		quit_with_error("Não foi possível colocar o socket em modo de escuta\n.");
	}

	length = sizeof(my_address);
	printf("Servidor vulnerável iniciado e em escuta...\n");

	/* Laço infinito em que o servidor receberá requisições */
	while (1) {
		/* Buffer de recebimento é zerado a cada nova conexão */
		for (index = 0; index < BUFFER_SIZE; index++)
		buffer[index] = '\0';

		/* Estabelecimento de conexão com o cliente */
		if ((incoming_socket = accept(socket_descriptor, (struct sockaddr *) &their_address, &length)) == -1) {
			cleanup(socket_descriptor, incoming_socket);
			quit_with_error("Não foi possível aceitar conexão.\n");
		}

		/* Impressão de texto de depuração */
		printf("Descritores dos sockets: Servidor: %d, Conexão: %d\n", socket_descriptor, incoming_socket);
		printf("Conexão a partir de %s...\n", inet_ntoa(their_address.sin_addr));
		send(incoming_socket, "Bem-vindo ao servidor vulnerável. Comporte-se...\n", 49, 0);
		index = 0;

		/* Leitura de mensagem enviada pelo cliente conectado */
		while ((message_length = read(incoming_socket, buffer + index, 1)) > 0) {
			index += message_length;
			if (buffer[index - 1] == '\0')
			break;
		}

		/* Impressão de texto de depuração */
		printf("Descritores dos sockets: Servidor: %d, Conexão: %d\n", socket_descriptor, incoming_socket);
		printf("Mensagem recebida: %s\n", buffer);

		/* Chamada da função de processamento da mensagem recebida */
		process(buffer);
		/* Fechamento da conexão com o cliente */
		close(incoming_socket);
	}
	
	/* Liberação do socket servidor */
	cleanup(socket_descriptor, incoming_socket);
	return 0;
}

/* Processamento da mensagem do cliente.
 * Apenas efetua cópia da string para buffer local, que poderá ser utilizado por outra thread de execução */
void process(char *buffer) {
	char local_buffer[100];
	strcpy(local_buffer, buffer);
}

void quit_with_error(char * error_message) {
	fprintf(stderr, "%s", error_message);
	exit(1);
}

void cleanup(int socket_descriptor, int incoming_socket) {
	if (socket_descriptor != -1)  {
		close(socket_descriptor);
		close(incoming_socket);
	}
}
