/* * Copyright (c) 2019, Natasha Kerensikova * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #include struct client { const char *secret; const char *user; const char *device; }; struct context { int port; struct evhttp_uri *target; struct evhttp_connection *ev_con; int nb_clients; struct client *clients; }; static void context_clear(struct context *ctx) { for (int i = 0; i < ctx->nb_clients; i++) { free((void *)ctx->clients[i].secret); free((void *)ctx->clients[i].user); free((void *)ctx->clients[i].device); } if (ctx->target) evhttp_uri_free(ctx->target); if (ctx->ev_con) evhttp_connection_free(ctx->ev_con); free(ctx->clients); ctx->target = NULL; ctx->ev_con = NULL; ctx->clients = NULL; ctx->nb_clients = 0; } static int add_client(struct context *ctx, const char *secret, const char *user, const char *device) { struct client *new_client; new_client = realloc(ctx->clients, (ctx->nb_clients + 1) * sizeof *new_client); if (!new_client) return -1; ctx->clients = new_client; new_client += ctx->nb_clients; new_client->user = strdup(user); new_client->secret = strdup(secret); new_client->device = strdup(device); if (!new_client->secret || !new_client->user || !new_client->device) return -1; ctx->nb_clients += 1; return 0; } static int add_client_line(struct context *ctx, char *line) { char *user; char *device; int i = 0; while (line[i] != '\t') { if (!line[i]) return 0; i++; } line[i++] = 0; user = line + i; while (line[i] != '\t') { if (!line[i]) return 0; i++; } line[i++] = 0; device = line + i; while (line[i] != 0 && line[i] != '\n') { if (line[i] == '\t') return 0; i++; } line[i] = 0; return add_client(ctx, line, user, device); } static int add_client_file(struct context *ctx, const char *filename) { char buffer[256]; FILE *f = fopen(filename, "r"); if (!f) { fprintf(stderr, "Unable to open file \"%s\": %s\n", filename, strerror(errno)); return -1; } while (fgets(buffer, sizeof buffer, f)) { if (add_client_line(ctx, buffer) < 0) { fprintf(stderr, "Unable to allocate client line\n"); return -1; } } fclose(f); return 0; } static const struct client * get_client(struct context *ctx, const char *secret) { for (int i = 0; i < ctx->nb_clients; i++) { const char *hash = crypt(secret, ctx->clients[i].secret); if (!strcmp(hash, ctx->clients[i].secret)) { return ctx->clients + i; } } return NULL; } static void http_request_done(struct evhttp_request *req, void *arg) { (void)arg; if (!req || !evhttp_request_get_response_code(req)) { fprintf(stderr, "Some error happened\n"); return; } } static void process_request_cb(struct evhttp_request *req, void *arg) { const char *query = evhttp_uri_get_query(evhttp_request_get_evhttp_uri(req)); if (query == NULL) { evhttp_send_reply(req, 200, "OK", NULL); return; } struct context *ctx = arg; struct evkeyvalq params; struct evkeyval *param; struct evhttp_request *output_req; struct evkeyvalq *output_headers; struct evbuffer *output_buffer; const struct client *client = NULL; char buffer[256] = "{\"_type\":\"location\""; size_t buf_size = 19; int ret; evhttp_parse_query_str(query, ¶ms); for (param = params.tqh_first; param; param = param->next.tqe_next) { if (!strcmp(param->key, "secret")) { client = get_client(ctx, param->value); if (client) { continue; } else { break; } } /* Guess whether it is a timestamp in milliseconds */ char *end = NULL; long l = strtol(param->value, &end, 10); if (end && !*end && l > 1500000000l) { /* Convert it to seconds */ ret = snprintf(buffer + buf_size, sizeof buffer - buf_size, ",\"%s\":%ld", param->key, (l + 500) / 1000); } else if (!strcmp(param->key, "tid")) { /* Copy the value as a string without change */ ret = snprintf(buffer + buf_size, sizeof buffer - buf_size, ",\"%s\":\"%s\"", param->key, param->value); } else { /* Copy the value as a number without change */ ret = snprintf(buffer + buf_size, sizeof buffer - buf_size, ",\"%s\":%s", param->key, param->value); } if (ret < 0 || buf_size + ret + 2 >= sizeof buffer) { evhttp_send_reply(req, 400, "Bad Request", NULL); return; } buf_size += ret; } buffer[buf_size++] = '}'; buffer[buf_size] = 0; evhttp_clear_headers(¶ms); if (!client) { evhttp_send_reply(req, 403, "Forbidden", NULL); return; } output_req = evhttp_request_new(http_request_done, NULL); if (output_req == NULL) { fprintf(stderr, "evhttp_request_new() failed\n"); evhttp_send_reply(output_req, 500, "Internal Error", NULL); return; } output_buffer = evhttp_request_get_output_buffer(output_req); evbuffer_add(output_buffer, buffer, buf_size); output_headers = evhttp_request_get_output_headers(output_req); evhttp_add_header(output_headers, "Connection", "close"); evhttp_add_header(output_headers, "X-Limit-U", client->user); evhttp_add_header(output_headers, "X-Limit-D", client->device); snprintf(buffer, sizeof buffer, "%zu", buf_size); evhttp_add_header(output_headers, "Content-Length", buffer); ret = evhttp_make_request(ctx->ev_con, output_req, EVHTTP_REQ_POST, evhttp_uri_get_path(ctx->target)); if (ret < 0) { fprintf(stderr, "evhttp_make_request() failed\n"); evhttp_send_reply(req, 500, "Internal Error", NULL); return; } evhttp_send_reply(req, 200, "OK", NULL); } static void do_term(int sig, short events, void *arg) { struct event_base *base = arg; (void)sig; (void)events; event_base_loopbreak(base); } static int make_connection(struct context *ctx, struct event_base *base) { int port = evhttp_uri_get_port(ctx->target); if (port == -1) port = 80; ctx->ev_con = evhttp_connection_base_new(base, NULL, /* Blocking DNS resolution */ evhttp_uri_get_host(ctx->target), port); return (ctx->ev_con == NULL) ? -1 : 0; } int main(int argc, char **argv) { struct event_base *base = NULL; struct evhttp *http = NULL; struct event *term = NULL; struct context ctx = { 0 }; /* Parse command line */ if (argc >= 2) { ctx.port = atoi(argv[1]); } if (argc != 4 || ctx.port <= 0 || ctx.port > 65535) { fprintf(stderr, "Usage: %s port clients.conf http://target/uri\n", argv[0]); return 1; } if (add_client_file(&ctx, argv[2])) { context_clear(&ctx); return 1; } if (ctx.nb_clients == 0) { fprintf(stderr, "No client found in \"%s\"\n", argv[2]); context_clear(&ctx); return 1; } ctx.target = evhttp_uri_parse(argv[3]); if (!ctx.target) { fprintf(stderr, "Unable to parse URI \"%s\"\n", argv[3]); context_clear(&ctx); return 1; } if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { context_clear(&ctx); return 1; } setbuf(stdout, NULL); setbuf(stderr, NULL); /* event_enable_debug_logging(EVENT_DBG_ALL); */ base = event_base_new(); if (!base) { fprintf(stderr, "event_base_new() failed\n"); context_clear(&ctx); return 1; } if (make_connection(&ctx, base) < 0) { fprintf(stderr, "make_connection() failed\n"); event_base_free(base); context_clear(&ctx); return 1; } http = evhttp_new(base); if (!http) { fprintf(stderr, "evhttp_new() failed\n"); evhttp_connection_free(ctx.ev_con); event_base_free(base); context_clear(&ctx); return 1; } evhttp_set_gencb(http, process_request_cb, &ctx); if (evhttp_bind_socket(http, "0.0.0.0", ctx.port) < 0) { fprintf(stderr, "Unable to bind to port %d\n", ctx.port); evhttp_free(http); context_clear(&ctx); event_base_free(base); return 1; } term = evsignal_new(base, SIGINT, do_term, base); if (!term) { fprintf(stderr, "evsignal_new() failed\n"); evhttp_free(http); context_clear(&ctx); event_base_free(base); return 1; } if (event_add(term, NULL) < 0) { fprintf(stderr, "evsignal_new() failed\n"); event_free(term); evhttp_free(http); context_clear(&ctx); event_base_free(base); return 1; } event_base_dispatch(base); event_free(term); evhttp_free(http); context_clear(&ctx); event_base_free(base); return 0; }