From 6528352e045a9a23847c549279afd89e4a80557c Mon Sep 17 00:00:00 2001 From: Devaev Maxim Date: Wed, 20 Mar 2019 16:15:26 +0300 Subject: [PATCH] http basic auth --- README.md | 2 +- README.ru.md | 2 +- src/base64.c | 71 ++++++++++++++++++++++++++++++++++++++++++++++ src/base64.h | 26 +++++++++++++++++ src/http.c | 80 ++++++++++++++++++++++++++++++++++++++++------------ src/http.h | 3 ++ src/main.c | 18 ++++++++---- 7 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 src/base64.c create mode 100644 src/base64.h diff --git a/README.md b/README.md index 5467c3f..5ae5ccb 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ | Streaming via UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | | Debug logs without recompiling,
performance statistics log,
access to HTTP broadcast parameters | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | | Access to webcam controls (focus, servos)
and settings such as brightness via HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | -| Option to serve files
with a built-in HTTP server, auth settings | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No 3 | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | +| Option to serve files
with a built-in HTTP server | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) No 3 | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Yes | Footnotes: * ```1``` Long before µStreamer, I made a [patch](https://github.com/jacksonliam/mjpg-streamer/pull/164) to add DV-timings support to mjpg-streamer and to keep it from hanging up no device disconnection. Alas, the patch is far from perfect and I can't guarantee it will work every time - mjpg-streamer's source code is very complicated and its structure is hard to understand. With this in mind, along with needing multithreading and JPEG hardware acceleration in the future, I decided to make my own stream server from scratch instead of supporting legacy code. diff --git a/README.ru.md b/README.ru.md index 103e0d2..f2f119d 100644 --- a/README.ru.md +++ b/README.ru.md @@ -16,7 +16,7 @@ | Стрим через UNIX domain socket | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | | Дебаг-логи без перекомпиляции,
логгирование статистики производительности,
возможность получения параметров
трансляции по HTTP | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | | Поддержка контролов веб-камер (фокус,
движение сервами) и всяких настроек,
типа яркости, через HTTP | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | -| Возможность сервить файлы встроенным
HTTP-сервером, настройки авторизации | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет 3 | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | +| Возможность сервить файлы встроенным
HTTP-сервером | ![#f03c15](https://placehold.it/15/f03c15/000000?text=+) Нет 3 | ![#00aa00](https://placehold.it/15/00aa00/000000?text=+) Есть | Сносочки: * ```1``` Еще до написания µStreamer, я запилил [патч](https://github.com/jacksonliam/mjpg-streamer/pull/164), добавляющий в mjpg-streamer поддержку DV-таймингов и предотвращающий его зависание при отключении устройства. Однако патч, увы, далек от совершенства и я не гарантирую его стопроцентную работоспособность, поскольку код mjpg-streamer чрезвычайно запутан и очень плохо структурирован. Учитывая это, а также то, что в дальнейшем мне потребовались многопоточность и аппаратное кодирование JPEG, было принято решение написать свой стрим-сервер с нуля, чтобы не тратить силы на поддержку лишнего легаси. diff --git a/src/base64.c b/src/base64.c new file mode 100644 index 0000000..f9a299c --- /dev/null +++ b/src/base64.c @@ -0,0 +1,71 @@ +/***************************************************************************** +# # +# uStreamer - Lightweight and fast MJPG-HTTP streamer. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +*****************************************************************************/ + + +#include +#include +#include + +#include "tools.h" +#include "base64.h" + + +static const char ENCODING_TABLE[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', + 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', + 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', + 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', + 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', + 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', +}; + +static const unsigned MOD_TABLE[] = {0, 2, 1}; + + +char *base64_encode(const unsigned char *str) { + size_t str_len = strlen((const char *)str); + size_t encoded_size = 4 * ((str_len + 2) / 3) + 1; // +1 for '\0' + char *encoded; + + A_CALLOC(encoded, encoded_size); + + for (unsigned str_index = 0, encoded_index = 0; str_index < str_len;) { + unsigned octet_a = (str_index < str_len ? (unsigned char)str[str_index++] : 0); + unsigned octet_b = (str_index < str_len ? (unsigned char)str[str_index++] : 0); + unsigned octet_c = (str_index < str_len ? (unsigned char)str[str_index++] : 0); + + unsigned triple = (octet_a << 0x10) + (octet_b << 0x08) + octet_c; + + encoded[encoded_index++] = ENCODING_TABLE[(triple >> 3 * 6) & 0x3F]; + encoded[encoded_index++] = ENCODING_TABLE[(triple >> 2 * 6) & 0x3F]; + encoded[encoded_index++] = ENCODING_TABLE[(triple >> 1 * 6) & 0x3F]; + encoded[encoded_index++] = ENCODING_TABLE[(triple >> 0 * 6) & 0x3F]; + } + + for (unsigned index = 0; index < MOD_TABLE[str_len % 3]; index++) { + encoded[encoded_size - 2 - index] = '='; + } + + encoded[encoded_size - 1] = '\0'; + return encoded; +} diff --git a/src/base64.h b/src/base64.h new file mode 100644 index 0000000..531e5df --- /dev/null +++ b/src/base64.h @@ -0,0 +1,26 @@ +/***************************************************************************** +# # +# uStreamer - Lightweight and fast MJPG-HTTP streamer. # +# # +# Copyright (C) 2018 Maxim Devaev # +# # +# This program is free software: you can redistribute it and/or modify # +# it under the terms of the GNU General Public License as published by # +# the Free Software Foundation, either version 3 of the License, or # +# (at your option) any later version. # +# # +# This program is distributed in the hope that it will be useful, # +# but WITHOUT ANY WARRANTY; without even the implied warranty of # +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # +# GNU General Public License for more details. # +# # +# You should have received a copy of the GNU General Public License # +# along with this program. If not, see . # +# # +*****************************************************************************/ + + +#include + + +char *base64_encode(const unsigned char *str); diff --git a/src/http.c b/src/http.c index 9f391eb..ee7f403 100644 --- a/src/http.c +++ b/src/http.c @@ -50,6 +50,7 @@ #include "logging.h" #include "encoder.h" #include "stream.h" +#include "base64.h" #include "http.h" #include "data/index_html.h" @@ -58,6 +59,7 @@ static bool _http_get_param_true(struct evkeyvalq *params, const char *key); static char *_http_get_param_uri(struct evkeyvalq *params, const char *key); +static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server); static void _http_callback_root(struct evhttp_request *request, void *arg); static void _http_callback_state(struct evhttp_request *request, void *v_server); @@ -99,7 +101,7 @@ struct http_server_t *http_server_init(struct stream_t *stream) { assert((run->http = evhttp_new(run->base))); evhttp_set_allowed_methods(run->http, EVHTTP_REQ_GET|EVHTTP_REQ_HEAD); - assert(!evhttp_set_cb(run->http, "/", _http_callback_root, NULL)); + assert(!evhttp_set_cb(run->http, "/", _http_callback_root, (void *)server)); assert(!evhttp_set_cb(run->http, "/state", _http_callback_state, (void *)server)); assert(!evhttp_set_cb(run->http, "/snapshot", _http_callback_snapshot, (void *)server)); assert(!evhttp_set_cb(run->http, "/stream", _http_callback_stream, (void *)server)); @@ -128,6 +130,10 @@ void http_server_destroy(struct http_server_t *server) { client = next; } + if (server->run->auth_token) { + free(server->run->auth_token); + } + free(server->run->exposed->picture.data); free(server->run->exposed); free(server->run); @@ -135,16 +141,18 @@ void http_server_destroy(struct http_server_t *server) { } int http_server_listen(struct http_server_t *server) { - struct timeval refresh_interval; + { + struct timeval refresh_interval; - refresh_interval.tv_sec = 0; - if (server->run->stream->dev->desired_fps > 0) { - refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2); - } else { - refresh_interval.tv_usec = 16000; // ~60fps + refresh_interval.tv_sec = 0; + if (server->run->stream->dev->desired_fps > 0) { + refresh_interval.tv_usec = 1000000 / (server->run->stream->dev->desired_fps * 2); + } else { + refresh_interval.tv_usec = 16000; // ~60fps + } + assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server))); + assert(!event_add(server->run->refresh, &refresh_interval)); } - assert((server->run->refresh = event_new(server->run->base, -1, EV_PERSIST, _http_exposed_refresh, server))); - assert(!event_add(server->run->refresh, &refresh_interval)); server->run->drop_same_frames_blank = max_u(server->drop_same_frames, server->run->drop_same_frames_blank); @@ -154,6 +162,23 @@ int http_server_listen(struct http_server_t *server) { evhttp_set_timeout(server->run->http, server->timeout); + if (server->user != NULL && strlen(server->user) > 0) { + char *passwd = (server->passwd != NULL ? server->passwd : ""); + char *raw_token; + char *encoded_token; + + A_CALLOC(raw_token, strlen(server->user) + strlen(passwd) + 2); + sprintf(raw_token, "%s:%s", server->user, passwd); + encoded_token = base64_encode((unsigned char *)raw_token); + free(raw_token); + + A_CALLOC(server->run->auth_token, strlen(encoded_token) + 16); + sprintf(server->run->auth_token, "Basic %s", encoded_token); + free(encoded_token); + + LOG_INFO("Using HTTP basic auth"); + } + if (server->unix_path) { struct sockaddr_un unix_addr; int unix_fd_flags; @@ -240,17 +265,36 @@ static char *_http_get_param_uri(struct evkeyvalq *params, const char *key) { #define ADD_HEADER(_key, _value) \ assert(!evhttp_add_header(evhttp_request_get_output_headers(request), _key, _value)) -#define PROCESS_HEAD_REQUEST { \ - if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \ - evhttp_send_reply(request, HTTP_OK, "OK", NULL); \ +static int _http_preprocess_request(struct evhttp_request *request, struct http_server_t *server) { + if (server->run->auth_token) { + const char *token = evhttp_find_header(evhttp_request_get_input_headers(request), "Authorization"); + + if (token == NULL || strcmp(token, server->run->auth_token) != 0) { + ADD_HEADER("WWW-Authenticate", "Basic realm=\"Restricted area\""); + evhttp_send_reply(request, 401, "Unauthorized", NULL); + return -1; + } + } + + if (evhttp_request_get_command(request) == EVHTTP_REQ_HEAD) { \ + evhttp_send_reply(request, HTTP_OK, "OK", NULL); \ + return -1; + } + + return 0; +} + +#define PREPROCESS_REQUEST { \ + if (_http_preprocess_request(request, server) < 0) { \ return; \ } \ } -static void _http_callback_root(struct evhttp_request *request, UNUSED void *arg) { +static void _http_callback_root(struct evhttp_request *request, UNUSED void *v_server) { + struct http_server_t *server = (struct http_server_t *)v_server; struct evbuffer *buf; - PROCESS_HEAD_REQUEST; + PREPROCESS_REQUEST; assert((buf = evbuffer_new())); assert(evbuffer_add_printf(buf, "%s", HTML_INDEX_PAGE)); @@ -265,7 +309,7 @@ static void _http_callback_state(struct evhttp_request *request, void *v_server) enum encoder_type_t encoder_run_type; unsigned encoder_run_quality; - PROCESS_HEAD_REQUEST; + PREPROCESS_REQUEST; # define ENCODER(_next) server->run->stream->encoder->_next @@ -319,7 +363,7 @@ static void _http_callback_snapshot(struct evhttp_request *request, void *v_serv struct evbuffer *buf; char header_buf[64]; - PROCESS_HEAD_REQUEST; + PREPROCESS_REQUEST; # define EXPOSED(_next) server->run->exposed->_next @@ -384,7 +428,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server unsigned short client_port; uuid_t uuid; - PROCESS_HEAD_REQUEST; + PREPROCESS_REQUEST; conn = evhttp_request_get_connection(request); if (conn != NULL) { @@ -431,7 +475,7 @@ static void _http_callback_stream(struct evhttp_request *request, void *v_server } } -#undef PROCESS_HEAD_REQUEST +#undef PREPROCESS_REQUEST static void _http_callback_stream_write(struct bufferevent *buf_event, void *v_client) { # define BOUNDARY "boundarydonotcross" diff --git a/src/http.h b/src/http.h index 2ea5bad..c3d8a66 100644 --- a/src/http.h +++ b/src/http.h @@ -70,6 +70,7 @@ struct http_server_runtime_t { struct event_base *base; struct evhttp *http; evutil_socket_t unix_fd; + char *auth_token; struct event *refresh; struct stream_t *stream; struct exposed_t *exposed; @@ -84,6 +85,8 @@ struct http_server_t { char *unix_path; bool unix_rm; mode_t unix_mode; + char *user; + char *passwd; unsigned drop_same_frames; bool slowdown; unsigned fake_width; diff --git a/src/main.c b/src/main.c index 3077a47..6cd7a20 100644 --- a/src/main.c +++ b/src/main.c @@ -79,11 +79,13 @@ static const struct option _LONG_OPTS[] = { {"unix", required_argument, NULL, 'u'}, {"unix-rm", no_argument, NULL, 'r'}, {"unix-mode", required_argument, NULL, 'o'}, + {"user", required_argument, NULL, 3000}, + {"passwd", required_argument, NULL, 3001}, {"drop-same-frames", required_argument, NULL, 'e'}, {"slowdown", no_argument, NULL, 'l'}, - {"fake-width", required_argument, NULL, 3001}, - {"fake-height", required_argument, NULL, 3002}, - {"server-timeout", required_argument, NULL, 3003}, + {"fake-width", required_argument, NULL, 3002}, + {"fake-height", required_argument, NULL, 3003}, + {"server-timeout", required_argument, NULL, 3004}, {"perf", no_argument, NULL, 5000}, {"verbose", no_argument, NULL, 5001}, @@ -159,6 +161,8 @@ static void _help(struct device_t *dev, struct encoder_t *encoder, struct http_s printf(" -u|--unix ─────────── Bind to UNIX domain socket. Default: disabled\n\n"); printf(" -r|--unix-rm ─────────────── Try to remove old UNIX socket file before binding. Default: disabled\n\n"); printf(" -o|--unix-mode ────── Set UNIX socket file permissions (like 777). Default: disabled\n\n"); + printf(" --user ────────────── HTTP basic auth user. Default: disabled\n\n"); + printf(" --passwd ───────────── HTTP basic auth passwd. Default: empty\n\n"); printf(" -e|--drop-same-frames ── Don't send same frames to clients, but no more than specified number.\n"); printf(" It can significantly reduce the outgoing traffic, but will increase\n"); printf(" the CPU loading. Don't use this option with analog signal sources\n"); @@ -274,11 +278,13 @@ static int _parse_options(int argc, char *argv[], struct device_t *dev, struct e case 'u': OPT_SET(server->unix_path, optarg); case 'r': OPT_SET(server->unix_rm, true); case 'o': OPT_CHMOD(server->unix_mode, "--unix-mode"); + case 3000: OPT_SET(server->user, optarg); + case 3001: OPT_SET(server->passwd, optarg); case 'e': OPT_UNSIGNED(server->drop_same_frames, "--drop-same-frames", 0, 30); case 'l': OPT_SET(server->slowdown, true); - case 3001: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920); - case 3002: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200); - case 3003: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60); + case 3002: OPT_UNSIGNED(server->fake_width, "--fake-width", 0, 1920); + case 3003: OPT_UNSIGNED(server->fake_height, "--fake-height", 0, 1200); + case 3004: OPT_UNSIGNED(server->timeout, "--server-timeout", 1, 60); case 5000: OPT_SET(log_level, LOG_LEVEL_PERF); case 5001: OPT_SET(log_level, LOG_LEVEL_VERBOSE);