diff options
Diffstat (limited to 'src/redirector.c')
-rw-r--r-- | src/redirector.c | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/src/redirector.c b/src/redirector.c new file mode 100644 index 0000000..b4d7a78 --- /dev/null +++ b/src/redirector.c @@ -0,0 +1,418 @@ +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <signal.h> +#include <pthread.h> +#include <time.h> +#include <assert.h> + +#include "redirector.h" +#include "socket.h" +#include "utils.h" +#include "log.h" + +typedef struct client_thread { + pthread_t self; + psocket client_sock; + char host_buf[NI_MAXHOST], service_buf[NI_MAXSERV]; + redirector_ctx *rdr_ctx; +} client_thread; + +typedef struct server_event { + redirector_ctx **rdr_ctx; + const size_t siz; + size_t last_accept_count; + time_t last_accept_stamp; +} server_event; + +typedef struct client_event { + const psocket *fwd_sock; + const client_thread *client_args; +} client_event; + +static forward_state +fwd_state_string(const forward_state c_state, const client_thread *args, + const psocket *fwd); +static int +redirector_mainloop(event_ctx **ev_ctx, redirector_ctx *rdr_ctx[], size_t siz) + __attribute__((noreturn)); +static int redirector_accept_client(event_ctx *ev_ctx, int fd, void *user_data); +static void * +client_mainloop(void *arg); +static int client_io(event_ctx *ev_ctx, int src_fd, void *user_data); + + +int redirector_init_ctx(redirector_ctx **ctx) +{ + forward_ctx *fwd; + + assert(ctx); + if (!*ctx) + *ctx = (redirector_ctx *) malloc(sizeof(**ctx)); + assert(*ctx); + + memset(*ctx, 0, sizeof(**ctx)); + fwd = &(*ctx)->fwd_ctx; + if (fwd_init_ctx(&fwd)) + return 1; + + return 0; +} + +void redirector_free_ctx(redirector_ctx **rdr_ctx) +{ + assert(rdr_ctx && *rdr_ctx); + + socket_close(&(*rdr_ctx)->fwd_ctx.sock); + socket_close(&(*rdr_ctx)->sock); + free(*rdr_ctx); + (*rdr_ctx) = NULL; +} + +int redirector_setup(redirector_ctx *ctx, + const char *listen_addr, const char *listen_port, + const char *host, const char *port) +{ + int s; + struct addrinfo *srv_addr = NULL; + + assert(ctx); + assert(listen_port); + + if (!listen_addr) + listen_addr = "0.0.0.0"; + D2("Try to listen on %s:%s and forward to %s:%s", + (listen_addr ? listen_addr : "*"), listen_port, + host, port); + s = socket_init_in(listen_addr, listen_port, &srv_addr); + if (s) { + E_GAIERR(s, "Could not initialise server socket"); + return 1; + } + if (socket_bind_in(&ctx->sock, &srv_addr)) { + E_STRERR("Could not bind server socket to %s:%s", + listen_addr, listen_port); + return 1; + } + if (socket_listen_in(&ctx->sock)) { + E_STRERR("Could not listen on server socket on %s:%s", + listen_addr, listen_port); + return 1; + } + + if (fwd_setup_client(&ctx->fwd_ctx, host, port)) + return 1; + if (fwd_validate_ctx(&ctx->fwd_ctx)) + return 1; + + return 0; +} + +int redirector_validate_ctx(const redirector_ctx *ctx) +{ + assert(ctx); + assert(ctx->sock.fd >= 0 && ctx->sock.addr_len > 0); + + return 0; +} + +int redirector_setup_event(redirector_ctx *rdr_ctx[], size_t siz, event_ctx **ev_ctx) +{ + int s; + + assert(rdr_ctx && ev_ctx); + assert(siz > 0 && siz < POTD_MAXFD); + + event_init(ev_ctx); + if (event_setup(*ev_ctx)) + return 1; + + for (size_t i = 0; i < siz; ++i) { + if (event_add_sock(*ev_ctx, &rdr_ctx[i]->sock)) { + return 1; + } + + s = socket_addrtostr_in(&rdr_ctx[i]->sock, + rdr_ctx[i]->host_buf, rdr_ctx[i]->service_buf); + if (s) { + E_GAIERR(s, "Convert socket address to string"); + return 1; + } + N("Redirector service listening on %s:%s", + rdr_ctx[i]->host_buf, rdr_ctx[i]->service_buf); + } + + return 0; +} + +pid_t redirector_daemonize(event_ctx **ev_ctx, redirector_ctx *rdr_ctx[], size_t siz) +{ + pid_t p; + int s; + size_t i; + + assert(rdr_ctx && ev_ctx); + assert(siz > 0 && siz < POTD_MAXFD); + + for (i = 0; i < siz; ++i) { + assert(rdr_ctx[i]); + s = socket_addrtostr_in(&rdr_ctx[i]->sock, + rdr_ctx[i]->host_buf, + rdr_ctx[i]->service_buf); + if (s) { + E_GAIERR(s, "Could not initialise server daemon socket"); + return 1; + } + } + + p = fork(); + switch (p) { + case -1: + W_STRERR("%s", "Server daemonize"); + return -1; + case 0: + if (change_default_user_group()) { + E_STRERR("%s", "Change user/group"); + return -1; + } + N("%s", "Server daemon mainloop"); + redirector_mainloop(ev_ctx, rdr_ctx, siz); + } + D2("Server daemon pid: %d", p); + + event_free(ev_ctx); + for (i = 0; i < siz; ++i) + redirector_free_ctx(&rdr_ctx[i]); + + return p; +} + +static forward_state +fwd_state_string(const forward_state c_state, const client_thread *args, + const psocket *fwd) +{ + switch (c_state) { + case CON_OK: + break; + case CON_IN_ERROR: + N("Lost connection to %s:%s: %d", + args->host_buf, args->service_buf, + args->client_sock.fd); + break; + case CON_IN_TERMINATED: + N("Connection terminated: %s:%s: %d", + args->host_buf, args->service_buf, + args->client_sock.fd); + break; + case CON_OUT_ERROR: + N("Lost forward connection to %s:%s: %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, + fwd->fd); + break; + case CON_OUT_TERMINATED: + N("Forward connection terminated: %s:%s: %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, + fwd->fd); + break; + } + + return c_state; +} + +static int redirector_mainloop(event_ctx **ev_ctx, redirector_ctx *rdr_ctx[], size_t siz) +{ + int rc; + server_event ev_srv = { rdr_ctx, siz, 0, 0 }; + + set_procname("[potd] redirector"); + assert( set_child_sighandler() == 0 ); + + ev_srv.last_accept_stamp = time(NULL); + rc = event_loop(*ev_ctx, redirector_accept_client, &ev_srv); + event_free(ev_ctx); + + exit(rc); +} + +static int redirector_accept_client(event_ctx *ev_ctx, int fd, void *user_data) +{ + size_t i; + double d; + int s; + time_t t; + server_event *ev_srv = (server_event *) user_data; + client_thread *args; + redirector_ctx *rdr_ctx; + + (void) ev_ctx; + assert(ev_srv); + + for (i = 0; i < ev_srv->siz; ++i) { + rdr_ctx = ev_srv->rdr_ctx[i]; + if (rdr_ctx->sock.fd == fd) { + args = (client_thread *) calloc(1, sizeof(*args)); + assert(args); + + if (socket_accept_in(&rdr_ctx->sock, + &args->client_sock)) + { + E_STRERR("Could not accept client connection on fd %d", + rdr_ctx->sock.fd); + goto error; + } + + ev_srv->last_accept_count++; + t = time(NULL); + d = difftime(t, ev_srv->last_accept_stamp); + if (d == 0.0f) + d = 1.0f; + if (d > 5.0f) { + ev_srv->last_accept_stamp = t; + ev_srv->last_accept_count = 0; + } + if (ev_srv->last_accept_count / (size_t)d > 3) { + W2("DoS protection: Got %zu accepts/s", + ev_srv->last_accept_count / (size_t)d); + goto error; + } + + args->rdr_ctx = rdr_ctx; + s = socket_addrtostr_in(&args->client_sock, + args->host_buf, args->service_buf); + if (s) { + E_GAIERR(s, "Convert socket address to string"); + goto error; + } + N2("New connection from %s:%s to %s:%s: %d", + args->host_buf, args->service_buf, + rdr_ctx->host_buf, rdr_ctx->service_buf, + args->client_sock.fd); + + if (pthread_create(&args->self, NULL, + client_mainloop, args)) + { + E_STRERR("Thread creation for %s:%s on fd %d", + args->host_buf, args->service_buf, + args->client_sock.fd); + goto error; + } + + return 1; +error: + socket_close(&args->client_sock); + free(args); + return 0; + } + } + + return 0; +} + +static void * +client_mainloop(void *arg) +{ + client_thread *args; + client_event ev_cli; + int s; + event_ctx *ev_ctx = NULL; + psocket fwd; + + assert(arg); + args = (client_thread *) arg; + pthread_detach(args->self); + + event_init(&ev_ctx); + if (event_setup(ev_ctx)) { + E_STRERR("Client event context creation for server fd %d", + args->rdr_ctx->sock.fd); + goto finish; + } + + if (fwd_connect_sock(&args->rdr_ctx->fwd_ctx, &fwd)) { + E_STRERR("Forward connection to %s:%s server fd %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, + args->rdr_ctx->sock.fd); + goto finish; + } + N("Forwarding connection from %s:%s to %s:%s forward fd %d", + args->host_buf, args->service_buf, + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, fwd.fd); + + if (event_add_sock(ev_ctx, &fwd)) { + E_STRERR("Forward event context add to %s:%s forward fd %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, fwd.fd); + goto finish; + } + + /* + * We got the client socket from our main thread, so fd flags like + * O_NONBLOCK are not inherited! + */ + s = socket_nonblock(&args->client_sock); + if (s) { + E_STRERR("Socket non blocking mode to %s:%s forward fd %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, fwd.fd); + goto finish; + } + if (event_add_sock(ev_ctx, &args->client_sock)) { + E_STRERR("Forward event context add to %s:%s forward fd %d", + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf, fwd.fd); + goto finish; + } + + ev_cli.client_args = args; + ev_cli.fwd_sock = &fwd; + if (event_loop(ev_ctx, client_io, &ev_cli) && ev_ctx->has_error) + E_STRERR("Forward connection data from %s:%s to %s:%s", + args->host_buf, args->service_buf, + args->rdr_ctx->fwd_ctx.host_buf, + args->rdr_ctx->fwd_ctx.service_buf); + +finish: + event_free(&ev_ctx); + socket_close(&fwd); + socket_close(&args->client_sock); + free(args); + return NULL; +} + +static int +client_io(event_ctx *ev_ctx, int src_fd, void *user_data) +{ + int dest_fd; + client_event *ev_cli = (client_event *) user_data; + const psocket *client_sock = &ev_cli->client_args->client_sock; + forward_state fwd_state; + + if (src_fd == ev_cli->fwd_sock->fd) { + dest_fd = client_sock->fd; + } else if (src_fd == client_sock->fd) { + dest_fd = ev_cli->fwd_sock->fd; + } else return 0; + + fwd_state = event_forward_connection(ev_ctx, dest_fd, NULL, NULL); + + switch (fwd_state_string(fwd_state, ev_cli->client_args, + ev_cli->fwd_sock)) + { + case CON_IN_TERMINATED: + case CON_OUT_TERMINATED: + ev_ctx->active = 0; + case CON_OK: + return 1; + case CON_IN_ERROR: + case CON_OUT_ERROR: + ev_ctx->active = 0; + return 0; + } + + return 1; +} |