// This is a personal academic project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <pthread.h>

#include <arpa/inet.h>

#include "hello.h"
#include "auth.h"
#include "request.h"
#include "buffer.h"
#include "selector.h"
#include "server.h"
#include "stm.h"
#include "socks5nio.h"
#include "netutils.h"
#include "parser.h"
#include "parser_utils.h"

#define N(x) (sizeof(x)/sizeof((x)[0]))
#define BUFF_SIZE 4096
#define POP3_COMMAND_ARG 249
#define USER_MAX_SIZE 249

bool auth_active = true;
bool pwd_dissector_active = false;

uint32_t total_connect = 0;
uint32_t now_connect = 0;
uint64_t bytes_transferred = 0;
int buff_size = 4096;
int timeout = 10;
int user_log;
int connection_log;

/** maquina de estados general */
enum socks_v5state {
    HELLO_READ,
    HELLO_WRITE,

    AUTH_READ,
    AUTH_WRITE,

    REQUEST_READ,
    REQUEST_RESOLV,
    REQUEST_CONNECTING,
    REQUEST_WRITE,

    COPY,

    DONE,
    HELLO_ERROR,
    AUTH_ERROR,
    REQUEST_ERROR,
    ERROR,
};

bool get_auth_status() {
    return auth_active;
}

bool set_auth_status(bool auth_status) {
    auth_active = auth_status;
    return true;
}

bool get_pass_dissector_status() {
    return pwd_dissector_active;
}

bool set_pass_dissector_status(bool pwd_dissector_status) {
    pwd_dissector_active = pwd_dissector_status;
    return true;
}

uint64_t get_bytes_transferred() {
    return bytes_transferred;
}

uint32_t get_now_connect() {
    return now_connect;
}

uint32_t get_total_connect() {
    return total_connect;
}

////////////////////////////////////////////////////////////////////
// Definición de variables para cada estado

/** usado por HELLO_READ, HELLO_WRITE */
struct hello_st {
    /** buffer utilizado para I/O */
    buffer               *rb, *wb;
    struct hello_parser   parser;
    /** el método de autenticación seleccionado */
    uint8_t               method;
    /** verifico haber elegido */
    char                  has_selected;
};

struct auth_st {
    /** buffer utilizado para I/O */
    buffer               *rb, *wb;
    struct auth_parser   parser;
    char * username;
    uint8_t verified;
};

struct request_st {
    buffer *rb, *wb;

    struct request request;
    struct request_parser parser;

    enum socks_response_status status;

    struct sockaddr_storage * origin_addr;
    socklen_t * origin_addr_len;
    int * origin_domain;

    const int * client_fd;
    int * origin_fd;
};

struct connecting {
    buffer * wb;
    const int * client_fd;
    int * origin_fd;
    enum socks_response_status * status;
};

struct copy {
    int * fd;
    buffer * rb, * wb;
    fd_interest duplex;

    struct parser * parser_user;
    struct parser * parser_pass;
    char * user;
    char * pass;
    bool user_done;
    bool pass_done;

    struct copy * other;
};

/*
 * Si bien cada estado tiene su propio struct que le da un alcance
 * acotado, disponemos de la siguiente estructura para hacer una única
 * alocación cuando recibimos la conexión.
 *
 * Se utiliza un contador de referencias (references) para saber cuando debemos
 * liberarlo finalmente, y un pool para reusar alocaciones previas.
 */
struct socks5 {
    struct sockaddr_storage client_addr;
    socklen_t client_addr_len;
    int client_fd;

    struct addrinfo * origin_resolution;
    struct addrinfo * origin_resolution_current;

    struct sockaddr_storage origin_addr;
    socklen_t origin_addr_len;
    int origin_domain;
    int origin_fd;

    /** maquinas de estados */
    struct state_machine          stm;

    /** estados para el client_fd */
    union {
        struct hello_st           hello;
        struct request_st         request;
        struct copy               copy;
        struct auth_st            auth;
    } client;
    /** estados para el origin_fd */
    union {
        struct connecting conn;
        struct copy copy;
    } orig;

    // TODO: decidir tamaño del buffer
    uint8_t raw_buff_a[BUFF_SIZE], raw_buff_b[BUFF_SIZE];
    buffer read_buffer, write_buffer;

    unsigned references;

    bool parser;
    char * username;

    struct socks5 * next;
};

static const unsigned max_pool = 50;
static unsigned pool_size = 0;
static struct socks5 * pool = 0;

static const struct state_definition * socks5_describe_states(void);
static struct parser_definition * user = NULL;
static struct parser_definition * pass = NULL;

static struct socks5 * socks5_new(int client_fd) {
    struct socks5 * ret;

    if (pool == NULL) {
        ret = malloc(sizeof(*ret));
    } else {
        ret = pool;
        pool = pool->next;
        ret->next = 0;
    }

    if (ret == NULL) {
        goto finally;
    }
    memset(ret, 0x00, sizeof(*ret));

    if (user == NULL && pass == NULL) {
        user = parser_utils_strcmpi("user ");
        pass = parser_utils_strcmpi("pass ");
    }

    ret->origin_fd = -1;
    ret->client_fd = client_fd;
    ret->client_addr_len = sizeof(ret->client_addr);

    ret->stm.initial = HELLO_READ;
    ret->stm.max_state = ERROR;
    ret->stm.states = socks5_describe_states();
    stm_init(&ret->stm);

    ret->parser = false;
    ret->username = NULL;

    buffer_init(&ret->read_buffer, N(ret->raw_buff_a), ret->raw_buff_a);
    buffer_init(&ret->write_buffer, N(ret->raw_buff_b), ret->raw_buff_b);

    ret->references = 1;
finally:
    return ret;
}

void parsers_def_destroy() {
    if (user != NULL) {
        parser_utils_strcmpi_destroy(user);
    }
    if (pass != NULL) {
        parser_utils_strcmpi_destroy(pass);
    }
}

void parsers_destroy(struct socks5 * s) {
    if (s->parser) {
        free(s->client.copy.user);
        free(s->client.copy.pass);
        parser_destroy(s->client.copy.parser_user);
        parser_destroy(s->client.copy.parser_pass);
    }
}

/** realmente destruye */
static void
socks5_destroy_(struct socks5 * s) {
    if (s->origin_resolution != NULL) {
        freeaddrinfo(s->origin_resolution);
        s->origin_resolution = 0;
    }
    if (s->username != NULL) {
        free(s->username);
        s->username = 0;
    }
    free(s);
}

/**
 * destruye un  `struct socks5', tiene en cuenta las referencias
 * y el pool de objetos.
 */
static void
socks5_destroy(struct socks5 * s) {
    if (s != NULL) {
        if (s->references == 1) {
            parsers_destroy(s);
            if (s->username != NULL) {
                free(s->username);
                s->username = 0;
            }
            if(s->origin_resolution != NULL) {
                freeaddrinfo(s->origin_resolution);
                s->origin_resolution = 0;
            }
            now_connect--;
            if(pool_size < max_pool) {
                s->next = pool;
                pool    = s;
                pool_size++;
            } else {
                socks5_destroy_(s);
            }
        } else {
            s->references -= 1;
        }
    }
}

void
socksv5_pool_destroy(void) {
    struct socks5 *next, *s;
    for(s = pool; s != NULL ; s = next) {
        next = s->next;
        socks5_destroy_(s);
    }
    parsers_def_destroy();
}

/** obtiene el struct (socks5 *) desde la llave de selección  */
#define ATTACHMENT(key) ( (struct socks5 *)(key)->data)

/* declaración forward de los handlers de selección de una conexión
 * establecida entre un cliente y el proxy.
 */
static void socksv5_read   (struct selector_key *key);
static void socksv5_write  (struct selector_key *key);
static void socksv5_block  (struct selector_key *key);
static void socksv5_close  (struct selector_key *key);
static const struct fd_handler socks5_handler = {
    .handle_read   = socksv5_read,
    .handle_write  = socksv5_write,
    .handle_close  = socksv5_close,
    .handle_block  = socksv5_block,
};

/** Intenta aceptar la nueva conexión entrante*/
void socksv5_passive_accept(struct selector_key *key) {
    struct sockaddr_storage       client_addr;
    socklen_t                     client_addr_len = sizeof(client_addr);
    struct socks5                *state           = NULL;

    const int client = accept(key->fd, (struct sockaddr*) &client_addr,
                                                          &client_addr_len);
    if(client == -1) {
        goto fail;
    }
    
    if(selector_fd_set_nio(client) == -1) {
        goto fail;
    }
    state = socks5_new(client);
    if (state == NULL) {
        // sin un estado, nos es imposible manejaro.
        // tal vez deberiamos apagar accept() hasta que detectemos
        // que se liberó alguna conexión.
        goto fail;
    }
    memcpy(&state->client_addr, &client_addr, client_addr_len);
    state->client_addr_len = client_addr_len;

    if(SELECTOR_SUCCESS != selector_register(key->s, client, &socks5_handler, OP_READ, state)) {
        goto fail;
    }
    now_connect++;
    total_connect++;
    return;
fail:
    if(client != -1) {
        close(client);
    }
    socks5_destroy(state);
}

////////////////////////////////////////////////////////////////////////////////
// HELLO
////////////////////////////////////////////////////////////////////////////////

/** callback del parser utilizado en `read_hello' */
static void on_hello_method(struct hello_parser * p, const uint8_t method) {
    uint8_t * selected = p->data;

    if (auth_active) {
        if (METHOD_USERNAME_PASSWORD_AUTHENTICATION == method) {
            *selected = method;
            p->has_selected = 1;
        }
    }
    else {
        if (METHOD_NO_AUTHENTICATION_REQUIRED == method) {
            *selected = method;
            p->has_selected = 1;
        }
    }
    if (!p->has_selected) {
        *selected = METHOD_NO_ACCEPTABLE_METHODS;
    }
}

/** inicializa las variables de los estados HELLO_… */
static void hello_read_init(const unsigned state, struct selector_key * key) {
    struct hello_st *d = &ATTACHMENT(key)->client.hello;

    d->rb = &(ATTACHMENT(key)->read_buffer);
    d->wb = &(ATTACHMENT(key)->write_buffer);
    d->parser.data = &d->method;
    d->parser.on_authentication_method = on_hello_method;
    d->parser.has_selected = 0;
    hello_parser_init(&d->parser);
}

static unsigned
hello_process(const struct hello_st * d);

/** lee todos los bytes del mensaje de tipo `hello' e inicia su proceso */
static unsigned hello_read(struct selector_key * key) {
    struct hello_st *d = &ATTACHMENT(key)->client.hello;
    unsigned ret = HELLO_READ;
    bool error = false;
    uint8_t *ptr;
    size_t  count;
    ssize_t  n;

    ptr = buffer_write_ptr(d->rb, &count);
    n = recv(key->fd, ptr, count, 0);
    if(n > 0) {
        buffer_write_adv(d->rb, n);
        const enum hello_state st = hello_consume(d->rb, &d->parser, &error);
        
        if(hello_is_done(st, 0)) {
            if(SELECTOR_SUCCESS == selector_set_interest_key(key, OP_WRITE)) {
                ret = hello_process(d);
            } else {
                ret = ERROR;
            }
        }
    } else {
        ret = ERROR;
    }

    if (error) {
        ret = HELLO_ERROR;
        selector_set_interest_key(key, OP_WRITE);
    }

    return ret;
}

/** procesamiento del mensaje `hello' */
static unsigned hello_process(const struct hello_st * d) {
    unsigned ret = HELLO_WRITE;

    uint8_t m = d->method;
    const uint8_t r = (m == METHOD_NO_ACCEPTABLE_METHODS) ? 0xFF : m;
    if (hello_marshall(d->wb, (enum socks_response_status) r) == -1) {
        ret = ERROR;
    }
    if (METHOD_NO_ACCEPTABLE_METHODS == m) {
        ret = HELLO_ERROR;
    }
    return ret;
}

static void hello_read_close(const unsigned state, struct selector_key * key) {
    struct hello_st * d = &ATTACHMENT(key)->client.hello;

    hello_parser_close(&d->parser);
}

static unsigned hello_write(struct selector_key * key) {
    struct hello_st * d = &ATTACHMENT(key)->client.hello;

    unsigned ret = HELLO_WRITE;
    uint8_t * ptr;
    size_t count;
    ssize_t n;

    ptr = buffer_read_ptr(d->wb, &count);
    n = send(key->fd, ptr, count, MSG_NOSIGNAL);
    if (n == -1) {
        ret = ERROR;
    } else {
        buffer_read_adv(d->wb, n);
        if (!buffer_can_read(d->wb)) {
            if (SELECTOR_SUCCESS == selector_set_interest_key(key, OP_READ)) {
                if (d->method == METHOD_NO_AUTHENTICATION_REQUIRED)
                    ret = REQUEST_READ;
                else if (d->method == METHOD_USERNAME_PASSWORD_AUTHENTICATION)
                    ret = AUTH_READ;
            } else {
                ret = ERROR;
            }
        }
    }

    return ret;
}

static unsigned hello_write_error(struct selector_key * key) {
    hello_write(key);
    return ERROR;
}

////////////////////////////////////////////////////////////////////////////////
// AUTH
////////////////////////////////////////////////////////////////////////////////

/** callback del parser utilizado en `read_auth' */
static void on_auth_username(struct auth_parser * p, char * username) {
    int * idx = &p->user_pos;
    *idx = findUser(username);
}

/** callback del parser utilizado en `read_auth' */
static void on_auth_password(struct auth_parser * p, char * password) {
    uint8_t * verified = p->verified;
    if (p->user_pos >= 0) {
        if (check_password(p->user_pos, password))
            *verified = 1;
    }
}

/** inicializa las variables de los estados HELLO_… */
static void auth_read_init(const unsigned state, struct selector_key * key) {
    struct auth_st *d = &ATTACHMENT(key)->client.auth;

    d->rb = &(ATTACHMENT(key)->read_buffer);
    d->wb = &(ATTACHMENT(key)->write_buffer);
    d->parser.verified = &d->verified;
    d->parser.on_username = on_auth_username;
    d->parser.on_password = on_auth_password;
    d->username = malloc(256);
    ATTACHMENT(key)->username = d->username;
    d->parser.username = d->username; 
    *d->parser.verified = 0;
    d->parser.idx = 0;
    auth_parser_init(&d->parser);
}

/** procesamiento del mensaje `auth' */
static unsigned auth_process(const struct auth_st * d);

/** lee todos los bytes del mensaje de tipo `auth' e inicia su proceso */
static unsigned auth_read(struct selector_key * key) {
    struct auth_st *d = &ATTACHMENT(key)->client.auth;
    unsigned ret = AUTH_READ;
    bool error = false;
    uint8_t *ptr;
    size_t  count;
    ssize_t  n;

    ptr = buffer_write_ptr(d->rb, &count);
    n = recv(key->fd, ptr, count, 0);
    if(n > 0) {
        buffer_write_adv(d->rb, n);
        const enum auth_state st = auth_consume(d->rb, &d->parser, &error);

        if(auth_is_done(st, 0)) {
            if(SELECTOR_SUCCESS == selector_set_interest_key(key, OP_WRITE)) {
                ret = auth_process(d);
            } else {
                ret = ERROR;
            }
        }
    } else {
        ret = ERROR;
    }

    if (error) {
        ret = AUTH_ERROR;
        selector_set_interest_key(key, OP_WRITE);
    }

    return ret;
}

/** procesamiento del mensaje `auth' */
static unsigned auth_process(const struct auth_st * d) {
    unsigned ret = AUTH_WRITE;

    uint8_t v = d->verified;
    const uint8_t r = v ? 0x00 : 0xFE;
    if (auth_marshall(d->wb, (enum socks_response_status) r) == -1) {
        ret  = ERROR;
    }
    if (!v) {
        ret = AUTH_ERROR;
    }
    return ret;
}

static void auth_read_close(const unsigned state, struct selector_key * key) {
    struct auth_st * d = &ATTACHMENT(key)->client.auth;

    auth_parser_close(&d->parser);
}

static unsigned auth_write(struct selector_key * key) {
    struct auth_st * d = &ATTACHMENT(key)->client.auth;

    unsigned ret = AUTH_WRITE;
    uint8_t * ptr;
    size_t count;
    ssize_t n;

    ptr = buffer_read_ptr(d->wb, &count);
    n = send(key->fd, ptr, count, MSG_NOSIGNAL);
    if (n == -1) {
        ret = ERROR;
    } else {
        buffer_read_adv(d->wb, n);
        if (!buffer_can_read(d->wb)) {
            if (SELECTOR_SUCCESS == selector_set_interest_key(key, OP_READ)) {
                ret = REQUEST_READ;
            } else {
                ret = ERROR;
            }
        }
    }

    return ret;
}

static unsigned auth_write_error(struct selector_key * key) {
    auth_write(key);
    return ERROR;
}

////////////////////////////////////////////////////////////////////////////////
// REQUEST
////////////////////////////////////////////////////////////////////////////////

static void request_init(const unsigned state, struct selector_key * key) {
    struct request_st * d = &ATTACHMENT(key)->client.request;

    d->rb = &(ATTACHMENT(key)->read_buffer);
    d->wb = &(ATTACHMENT(key)->write_buffer);
    d->parser.request = &d->request;
    d->status = status_general_SOCKS_server_failure;
    request_parser_init(&d->parser);
    d->client_fd = &ATTACHMENT(key)->client_fd;
    d->origin_fd = &ATTACHMENT(key)->origin_fd;

    d->origin_addr = &ATTACHMENT(key)->origin_addr;
    d->origin_addr_len = &ATTACHMENT(key)->origin_addr_len;
    d->origin_domain = &ATTACHMENT(key)->origin_domain;
}

static unsigned request_process(struct selector_key * key, struct request_st * d);

static unsigned request_read(struct selector_key * key) {
    struct request_st * d = &ATTACHMENT(key)->client.request;

    buffer *b = d->rb;
    unsigned ret = REQUEST_READ;
    bool error = false;
    uint8_t * ptr;
    size_t count;
    ssize_t n;
    int st;

    ptr = buffer_write_ptr(b, &count);
    n = recv(key->fd, ptr, count, 0);
    if (n > 0) {
        buffer_write_adv(b, n);
        st = request_consume(b, &d->parser, &error);
        if (request_is_done(st, 0)) {
            ret = request_process(key, d);
        }
    } else {
        ret = ERROR;
    }

    if (error) {
        request_marshall(d->wb, d->status);
        ret = REQUEST_ERROR;
        selector_set_interest_key(key, OP_WRITE);
    }

    return ret;
}

static unsigned request_connect(struct selector_key * key, struct request_st * d);
static void * request_resolv_blocking(void * data);

static unsigned request_process(struct selector_key * key, struct request_st * d) {
    unsigned ret;
    pthread_t tid;

    switch (d->request.cmd) {
        case socks_req_cmd_connect:
            switch (d->request.dest_addr_type) {
                case socks_req_addr_ipv4: {
                    ATTACHMENT(key)->origin_domain = AF_INET;
                    d->request.dest_addr.ipv4.sin_port = d->request.dest_port;
                    ATTACHMENT(key)->origin_addr_len = sizeof(d->request.dest_addr.ipv4);
                    memcpy(&ATTACHMENT(key)->origin_addr, &d->request.dest_addr, sizeof(d->request.dest_addr.ipv4));
                    ret = request_connect(key, d);
                    break;
                } case socks_req_addr_ipv6: {
                    ATTACHMENT(key)->origin_domain = AF_INET6;
                    d->request.dest_addr.ipv6.sin6_port = d->request.dest_port;
                    ATTACHMENT(key)->origin_addr_len = sizeof(d->request.dest_addr.ipv6);
                    memcpy(&ATTACHMENT(key)->origin_addr, &d->request.dest_addr, sizeof(d->request.dest_addr.ipv6));
                    ret = request_connect(key, d);
                    break;
                } case socks_req_addr_domain: {
                    struct selector_key * k = malloc(sizeof(*key));
                    if (k == NULL) {
                        ret = REQUEST_ERROR;
                        d->status = status_general_SOCKS_server_failure;
                        selector_set_interest_key(key, OP_WRITE);
                    } else {
                        memcpy(k, key, sizeof(*k));
                        if (-1 == pthread_create(&tid, 0, request_resolv_blocking, k)) {
                            ret = REQUEST_ERROR;
                            d->status = status_general_SOCKS_server_failure;
                            selector_set_interest_key(key, OP_WRITE);
                        } else {
                            ret = REQUEST_RESOLV;
                            selector_set_interest_key(key, OP_NOOP);
                        }
                    }
                    break;
                } default: {
                    d->status = status_address_type_not_supported;
                    request_marshall(d->wb, d->status);
                    ret = REQUEST_ERROR;
                    selector_set_interest_key(key, OP_WRITE);
                    break;
                }
            }
            break;
        case socks_req_cmd_bind:
        case socks_req_cmd_associate:
        default:
            d->status = status_command_not_supported;
            request_marshall(d->wb, d->status);
            ret = REQUEST_ERROR;
            selector_set_interest_key(key, OP_WRITE);
            break;
    }

    return ret;
}

static void * request_resolv_blocking(void * data) {
    struct selector_key * key = (struct selector_key *) data;
    struct socks5 * s = ATTACHMENT(key);

    pthread_detach(pthread_self());
    s->origin_resolution = 0;
    struct addrinfo hints = {
            .ai_family = AF_UNSPEC,
            .ai_socktype = SOCK_STREAM,
            .ai_flags = AI_PASSIVE,
            .ai_protocol = 0,
            .ai_canonname = NULL,
            .ai_addr = NULL,
            .ai_next = NULL,
    };
    
    char buff[7];
    snprintf(buff, sizeof(buff), "%d", ntohs(s->client.request.request.dest_port));
    int ret = getaddrinfo(s->client.request.request.dest_addr.fqdn, buff, &hints, &s->origin_resolution);

    if (ret != 0) {
        switch (ret) {
            case EAI_FAMILY:
                s->client.request.status = status_address_type_not_supported;
                break;
            case EAI_FAIL:
                s->client.request.status = status_host_unreachable;
                break;
            default:
                s->client.request.status = status_network_unreachable;
                break;
        }
    }

    selector_notify_block(key->s, key->fd);
    free(data);
    return 0;
}

static unsigned request_resolv_done(struct selector_key * key) {
    struct request_st * d = &ATTACHMENT(key)->client.request;
    struct socks5 * s = ATTACHMENT(key);
    unsigned int st = REQUEST_CONNECTING;

    if (d->status == status_address_type_not_supported || d->status == status_host_unreachable || d->status == status_network_unreachable) {
        return request_connect(key, d);
    }

    if (s->origin_resolution == 0) {
        d->status = status_general_SOCKS_server_failure;
    }

    s->origin_resolution_current = s->origin_resolution;
    st = request_connect(key, d);
    return st;
}

static unsigned request_connect(struct selector_key * key, struct request_st * d) {
    bool error = false;
    enum socks_response_status status = d->status;
    int *fd = d->origin_fd;
    struct socks5 * s = ATTACHMENT(key);

    if (s->origin_resolution_current != NULL) {
        s->origin_domain = s->origin_resolution_current->ai_family;
        s->origin_addr_len = s->origin_resolution_current->ai_addrlen;
        memcpy(&s->origin_addr, s->origin_resolution_current->ai_addr, s->origin_resolution_current->ai_addrlen);
    }

    *fd = socket(ATTACHMENT(key)->origin_domain, SOCK_STREAM, 0);
    if (*fd == -1) {
        error = true;
        goto finally;
    }
    if (selector_fd_set_nio(*fd) == -1) {
        goto finally;
    }

    if (-1 == connect(*fd, (const struct sockaddr *) &ATTACHMENT(key)->origin_addr, ATTACHMENT(key)->origin_addr_len)) {
        if (errno == EINPROGRESS) {
            selector_status st = selector_set_interest_key(key, OP_NOOP);
            if (SELECTOR_SUCCESS != st) {
                error = true;
                goto finally;
            }

            st = selector_register(key->s, *fd, &socks5_handler, OP_WRITE, key->data);
            if (SELECTOR_SUCCESS != st) {
                error = true;
                goto finally;
            }
            ATTACHMENT(key)->references += 1;
        } else {
            if (s->origin_resolution_current == NULL || s->origin_resolution_current->ai_next == NULL) {
                status = errno_to_socks(errno);
                error = true;
                goto finally;
            }
        }
    } else {
        abort();
    }
    
finally:
    if (error) {
        if (*fd != -1) {
            close(*fd);
            *fd = -1;
        }

        request_marshall(d->wb, d->status);
        selector_set_interest_key(key, OP_WRITE);
        return REQUEST_ERROR;
    }
    d->status = status;
    
    return REQUEST_CONNECTING;
}

static void request_read_close(const unsigned state, struct selector_key * key) {
    struct request_st * d = &ATTACHMENT(key)->client.request;
    request_close(&d->parser);
}

static void request_connecting_init(const unsigned state, struct selector_key *key){
    struct connecting * d = &ATTACHMENT(key)->orig.conn;

    d->client_fd = &ATTACHMENT(key)->client_fd;
    d->origin_fd = &ATTACHMENT(key)->origin_fd;
    d->status = &ATTACHMENT(key)->client.request.status;
    d->wb = &ATTACHMENT(key)->write_buffer;
}

static unsigned request_connecting(struct selector_key * key) {
    int error = 0;
    socklen_t len = sizeof(error);
    struct connecting * d = &ATTACHMENT(key)->orig.conn;
    struct request_st * req = &ATTACHMENT(key)->client.request;
    struct socks5 * sx = ATTACHMENT(key);

    if (getsockopt(key->fd, SOL_SOCKET, SO_ERROR, &error, &len) < 0 || error) {
        if (sx->origin_resolution_current != NULL && sx->origin_resolution_current->ai_next != NULL) {
            sx->origin_resolution_current = sx->origin_resolution_current->ai_next;
            return request_connect(key, req);
        }
        *d->status = errno_to_socks(error);
    } else {
        *d->status = status_succeeded;
        *d->origin_fd = key->fd;
    }

    if (-1 == request_marshall(d->wb, *d->status)) {
        *d->status = status_general_SOCKS_server_failure;
        abort();
    }
    selector_status s = 0;
    s |= selector_set_interest(key->s, *d->client_fd, OP_WRITE);
    s |= selector_set_interest_key(key, OP_NOOP);
    return SELECTOR_SUCCESS == s ? REQUEST_WRITE : ERROR;
}

void log_request_access(enum socks_response_status status, const struct sockaddr * client_addr, const struct sockaddr * origin_addr, char * username) {
    char cbuff[SOCKADDR_TO_HUMAN_MIN * 2 + 2 + 32] = {0};
    unsigned n = N(cbuff);
    time_t now = 0;
    time(&now);

    strftime(cbuff, n, "%FT%TZ ", gmtime(&now));
    size_t len = strlen(cbuff);
    sockaddr_to_human(cbuff + len, N(cbuff) - len, client_addr);
    strncat(cbuff, " ", n - 1);
    cbuff[n - 1] = 0;
    len = strlen(cbuff);
    sockaddr_to_human(cbuff + len, N(cbuff) - len, origin_addr);

    if (username != NULL) {
        fprintf(stdout, "register=A user=%s %s status=%d\n", username, cbuff, status);
    } else {
        fprintf(stdout, "register=A anon %s status=%d\n", cbuff, status);
    }

    FILE * file = fopen("access.log", "a");
    if (file == NULL)
        return;
    if (username != NULL) {
        fprintf(file, "register=A user=%s %s status=%d\n", username, cbuff, status);
    } else {
        fprintf(file, "register=A anon %s status=%d\n", cbuff, status);
    }
    fclose(file);
}

void log_request_password(const struct sockaddr * origin_addr, char * username, char * diss_name, char * diss_pass) {
    char cbuff[SOCKADDR_TO_HUMAN_MIN * 2 + 2 + 32] = {0};
    unsigned n = N(cbuff);
    time_t now = 0;
    time(&now);

    strftime(cbuff, n, "%FT%TZ ", gmtime(&now));
    size_t len = strlen(cbuff);
    sockaddr_to_human(cbuff + len, N(cbuff) - len, origin_addr);
    in_port_t port = 0;
    switch(origin_addr->sa_family) {
        case AF_INET:
            port =  ((struct sockaddr_in *) origin_addr)->sin_port;
            break;
        case AF_INET6:
            port =  ((struct sockaddr_in6 *) origin_addr)->sin6_port;
            break;
    }
    char * protocol;
    if (ntohs(port) == 110) {
        protocol = "POP3";
    }
    else {
        protocol = "UNK(POP3)";
    }

    if (username != NULL) {
        fprintf(stdout, "register=P user=%s %s protocol=%s diss_user=%s diss_pass=%s\n", username, cbuff, protocol, diss_name, diss_pass);
    } else {
        fprintf(stdout, "register=P anon %s protocol=%s diss_user=%s diss_pass=%s\n", cbuff, protocol, diss_name, diss_pass);
    }

    FILE * file = fopen("passwords.log", "a");
    if (file == NULL)
        return;
    if (username != NULL) {
        fprintf(file, "register=P user=%s %s protocol=%s diss_user=%s diss_pass=%s\n", username, cbuff, protocol, diss_name, diss_pass);
    } else {
        fprintf(file, "register=P anon %s protocol=%s diss_user=%s diss_pass=%s\n", cbuff, protocol, diss_name, diss_pass);
    }
    fclose(file);
}

static unsigned request_write(struct selector_key * key) {
    struct request_st * d = &ATTACHMENT(key)->client.request;

    unsigned ret = REQUEST_WRITE;
    buffer * b = d->wb;
    uint8_t * ptr;
    size_t count;
    ssize_t n;

    ptr = buffer_read_ptr(b, &count);
    n = send(key->fd, ptr, count, MSG_NOSIGNAL);
    if (-1 == n){
        ret = ERROR;
    } else {
        buffer_read_adv(b, n);

        if (!buffer_can_read(b)){
            if (d->status == status_succeeded) {
                ret = COPY;
                selector_set_interest(key->s, *d->client_fd, OP_READ);
                selector_set_interest(key->s, *d->origin_fd, OP_READ);
            } else {
                ret = DONE;
                selector_set_interest(key->s, *d->client_fd, OP_NOOP);
                if (-1 == *d->origin_fd) {
                    selector_set_interest(key->s, *d->origin_fd, OP_NOOP);
                }
            }
        }
    }

    log_request_access(d->status, (const struct sockaddr *) &ATTACHMENT(key)->client_addr, (const struct sockaddr *) &ATTACHMENT(key)->origin_addr, ATTACHMENT(key)->username);
    return ret;
}

static unsigned request_write_error(struct selector_key * key) {
    request_write(key);
    return ERROR;
}

static void copy_init(const unsigned state, struct selector_key * key) {
    struct copy * d = &ATTACHMENT(key)->client.copy;

    d->fd = &ATTACHMENT(key)->client_fd;
    d->rb = &ATTACHMENT(key)->read_buffer;
    d->wb = &ATTACHMENT(key)->write_buffer;
    d->duplex = OP_READ | OP_WRITE;
    d->other = &ATTACHMENT(key)->orig.copy;

    if (pwd_dissector_active) {
        d->user_done = false;
        d->pass_done = false;
        d->parser_user = parser_init(parser_no_classes(), user);
        d->parser_pass = parser_init(parser_no_classes(), pass);
        d->user = malloc(USER_MAX_SIZE);
        d->pass = malloc(USER_MAX_SIZE);
        ATTACHMENT(key)->parser = true;
    }
    else {
        d->parser_user = NULL;
        d->parser_pass = NULL;
    }

    d = &ATTACHMENT(key)->orig.copy;
    d->fd = &ATTACHMENT(key)->origin_fd;
    d->rb = &ATTACHMENT(key)->write_buffer;
    d->wb = &ATTACHMENT(key)->read_buffer;
    d->duplex = OP_READ | OP_WRITE;
    d->other = &ATTACHMENT(key)->client.copy;

    d->parser_user = NULL;
    d->parser_pass = NULL;
}

static unsigned copy_compute_interests(fd_selector s, struct copy * d) {
    fd_interest ret = OP_NOOP;
    if ((d->duplex & OP_READ) && buffer_can_write(d->rb)) {
        ret |= OP_READ;
    }
    if ((d->duplex & OP_WRITE) && buffer_can_read(d->wb)) {
        ret |= OP_WRITE;
    }
    
    if (SELECTOR_SUCCESS != selector_set_interest(s, *d->fd, ret)) {
        abort();
    }

    return ret;
}

static struct copy * copy_ptr(struct selector_key * key) {
    struct copy * d = &ATTACHMENT(key)->client.copy;
    if (*d->fd != key->fd) {
        d = d->other;
    }
    return d;
}

static unsigned copy_r(struct selector_key * key) {
    struct copy *d = copy_ptr(key);

    assert(*d->fd == key->fd);

    size_t size;
    ssize_t n;
    buffer * b = d->rb;
    unsigned ret = COPY;

    uint8_t * ptr = buffer_write_ptr(b, &size);
    n = recv(key->fd, ptr, size, 0);
    if (n <= 0) {

        shutdown(*d->fd, SHUT_RD);
        d->duplex &= ~OP_READ;
        if (*d->other->fd != -1) {
            shutdown(*d->other->fd, SHUT_WR);
            d->other->duplex &= ~OP_WRITE;
        }
    } else {
        buffer_write_adv(b, n);
                
        if (d->parser_user != NULL && pwd_dissector_active) { 
            struct parser_event * st = NULL;
            for (int i = 0, k = 0; i < n; i++) {
                if (st != NULL && st->type == STRING_CMP_NEQ) {
                    parser_reset(d->parser_user);
                    parser_reset(d->parser_pass);
                }
                if ((st == NULL || st->type != STRING_CMP_EQ) && !d->user_done) {
                    st = parser_feed(d->parser_user, *(ptr + i));
                }
                else if (st == NULL || st->type != STRING_CMP_EQ) {
                    st = parser_feed(d->parser_pass, *(ptr + i));
                }
                else if (!d->user_done && st->type == STRING_CMP_EQ) {
                    d->user[k++] = *(ptr + i);
                    if (*(ptr + i) == '\n') {
                        d->user_done = true;
                        d->user[k - 2] = 0;
                        k = 0;
                    }
                }
                else if (!d->pass_done && st->type == STRING_CMP_EQ) {
                    if (*(ptr + i) == '\n') {
                        d->pass_done = true;
                        d->pass[k - 1] = 0;
                        log_request_password((const struct sockaddr *) &ATTACHMENT(key)->origin_addr, ATTACHMENT(key)->username, d->user, d->pass);
                        d->user_done = false;
                        d->pass_done = false;
                        parser_reset(d->parser_user);
                        parser_reset(d->parser_pass);
                    }
                    d->pass[k++] = *(ptr + i);
                }
            }
            parser_reset(d->parser_user);
            parser_reset(d->parser_pass);
        }

    }
    copy_compute_interests(key->s, d);
    copy_compute_interests(key->s, d->other);
    if (d->duplex == OP_NOOP) {
        ret = DONE;
    }
    return ret;
}

static unsigned copy_w(struct selector_key * key) {
    struct copy * d = copy_ptr(key);

    assert(*d->fd == key->fd);
    size_t size;
    ssize_t n;
    buffer * b = d->wb;
    unsigned ret = COPY;

    uint8_t * ptr = buffer_read_ptr(b, &size);

    n = send(key->fd, ptr, size, MSG_NOSIGNAL);
    if (n == -1) {
        shutdown(*d->fd, SHUT_WR);
        d->duplex &= ~OP_WRITE;
        if (*d->other->fd != -1) {
            shutdown(*d->other->fd, SHUT_RD);
            d->other->duplex &= ~OP_READ;
        }
    } else {
        bytes_transferred += n;
        buffer_read_adv(b, n);
    }
    copy_compute_interests(key->s, d);
    copy_compute_interests(key->s, d->other);
    if (d->duplex == OP_NOOP) {
        ret = DONE;
    }
    return ret;
}

/** definición de handlers para cada estado */
static const struct state_definition client_statbl[] = {
    {
        .state            = HELLO_READ,
        .on_arrival       = hello_read_init,
        .on_departure     = hello_read_close,
        .on_read_ready    = hello_read,
    }, {
        .state = HELLO_WRITE,
        .on_write_ready = hello_write,
    }, {
        .state            = AUTH_READ,
        .on_arrival       = auth_read_init,
        .on_departure     = auth_read_close,
        .on_read_ready    = auth_read,
    }, {
        .state = AUTH_WRITE,
        .on_write_ready = auth_write,
    }, {
        .state = REQUEST_READ,
        .on_arrival = request_init,
        .on_departure = request_read_close,
        .on_read_ready = request_read,
    }, {
        .state = REQUEST_RESOLV,
        .on_block_ready = request_resolv_done,
    }, {
        .state = REQUEST_CONNECTING,
        .on_arrival = request_connecting_init,
        .on_write_ready = request_connecting,
    }, {
        .state = REQUEST_WRITE,
        .on_write_ready = request_write,
    }, {
        .state = COPY,
        .on_arrival = copy_init,
        .on_read_ready = copy_r,
        .on_write_ready = copy_w,
    }, {
        .state = DONE,
    }, {
        .state = HELLO_ERROR,
        .on_write_ready = hello_write_error,
    }, {
        .state = AUTH_ERROR,
        .on_write_ready = auth_write_error,
    }, { 
        .state = REQUEST_ERROR,
        .on_write_ready = request_write_error,
    }, {
        .state = ERROR,
    }
};

static const struct state_definition * socks5_describe_states(void) {
    return client_statbl;
}

///////////////////////////////////////////////////////////////////////////////
// Handlers top level de la conexión pasiva.
// son los que emiten los eventos a la maquina de estados.
static void
socksv5_done(struct selector_key * key);

static void
socksv5_read(struct selector_key * key) {
    struct state_machine *stm   = &ATTACHMENT(key)->stm;
    const enum socks_v5state st = stm_handler_read(stm, key);

    if(ERROR == st || DONE == st) {
        socksv5_done(key);
    }
}

static void
socksv5_write(struct selector_key * key) {
    struct state_machine *stm   = &ATTACHMENT(key)->stm;
    const enum socks_v5state st = stm_handler_write(stm, key);

    if(ERROR == st || DONE == st) {
        socksv5_done(key);
    }
}

static void
socksv5_block(struct selector_key * key) {
    struct state_machine * stm   = &ATTACHMENT(key)->stm;
    const enum socks_v5state st = stm_handler_block(stm, key);

    if(ERROR == st || DONE == st) {
        socksv5_done(key);
    }
}

static void
socksv5_close(struct selector_key * key) {
    socks5_destroy(ATTACHMENT(key));
}

static void
socksv5_done(struct selector_key * key) {
    const int fds[] = {
        ATTACHMENT(key)->client_fd,
        ATTACHMENT(key)->origin_fd,
    };
    for(unsigned i = 0; i < N(fds); i++) {
        if(fds[i] != -1) {
            if(SELECTOR_SUCCESS != selector_unregister_fd(key->s, fds[i])) {
                abort();
            }
            close(fds[i]);
        }
    }
}
