diff --git a/CMakeLists.txt b/CMakeLists.txt index 28c9db1..5b88404 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -22,6 +22,7 @@ add_library(webfuse_static STATIC src/webfuse/response_type.cpp src/webfuse/util/commandline_args.cpp src/webfuse/util/commandline_reader.cpp + src/webfuse/util/authenticator.cpp src/webfuse/filesystem.cpp src/webfuse/filesystem/status.cpp src/webfuse/filesystem/accessmode.cpp diff --git a/script/authenticator.sh b/script/authenticator.sh new file mode 100755 index 0000000..5a4bf93 --- /dev/null +++ b/script/authenticator.sh @@ -0,0 +1,13 @@ +#!/usr/bin/bash + +AUTH_TOKEN="$1" + +if [[ "$AUTH_TOKEN" == "user:bob;token=foo" ]] +then + echo "$(date): webfuse: auth granted: $AUTH_TOKEN" >> /tmp/webfuse_auth.log +else + echo "$(date): webfuse: auth denied: $AUTH_TOKEN" >> /tmp/webfuse_auth.log + exit 1 +fi + + diff --git a/script/provider.py b/script/provider.py index 9a87edc..c5cfaba 100755 --- a/script/provider.py +++ b/script/provider.py @@ -272,7 +272,7 @@ class FilesystemProvider: } async def run(self): - async with websockets.connect(self.url) as connection: + async with websockets.connect(self.url, extra_headers=[("X-Auth-Token", "user:bob;token=foo")]) as connection: while True: request = await connection.recv() reader = MessageReader(request) diff --git a/src/webfuse/util/authenticator.cpp b/src/webfuse/util/authenticator.cpp new file mode 100644 index 0000000..013ec7f --- /dev/null +++ b/src/webfuse/util/authenticator.cpp @@ -0,0 +1,55 @@ +#include "webfuse/util/authenticator.hpp" + +#include +#include +#include + +namespace webfuse +{ + +authenticator::authenticator(std::string const & app) +: app_(app) +{ + +} + +bool authenticator::authenticate(std::string const & token) +{ + bool result = false; + + pid_t const pid = fork(); + + if (pid == 0) + { + // child + + // prepare file descriptors + closefrom(0); + open("/dev/null", O_RDONLY); + open("/dev/null", O_WRONLY); + dup2(STDOUT_FILENO, STDERR_FILENO); + + execl(app_.c_str(), app_.c_str(), token.c_str(), nullptr); + exit(EXIT_FAILURE); + } + else if (pid > 0) + { + // parent + int exit_status = EXIT_FAILURE; + + int status = 0; + int const rc = waitpid(pid, &status, 0); + if (rc == pid) + { + exit_status = WEXITSTATUS(status); + } + + result = (exit_status == EXIT_SUCCESS); + } + + return result; +} + + + +} \ No newline at end of file diff --git a/src/webfuse/util/authenticator.hpp b/src/webfuse/util/authenticator.hpp new file mode 100644 index 0000000..348deb9 --- /dev/null +++ b/src/webfuse/util/authenticator.hpp @@ -0,0 +1,23 @@ +#ifndef WEBFUSE_AUTHENTICATOR_HPP +#define WEBFUSE_AUTHENTICATOR_HPP + +#include + +namespace webfuse +{ + +class authenticator +{ +public: + explicit authenticator(std::string const & app); + ~authenticator() = default; + + bool authenticate(std::string const & token); + +private: + std::string app_; +}; + +} + +#endif diff --git a/src/webfuse/webfuse.cpp b/src/webfuse/webfuse.cpp index a172d1e..cdedd04 100644 --- a/src/webfuse/webfuse.cpp +++ b/src/webfuse/webfuse.cpp @@ -30,10 +30,12 @@ int app::run(int argc, char * argv[]) // NOLINT(readability-convert-member-funct fuse::print_usage(); std::cout << R"( WEBFUSE options: - --wf-port PORT port number of websocket server (default: 8081) - --wf-vhost VHOST name of the virtual host (default: localhost) - --wf-cert PATH path of the server's public certificate (optional) - --wf-key PATH path of the server's private key (optional) + --wf-port PORT port number of websocket server (default: 8081) + --wf-vhost VHOST name of the virtual host (default: localhost) + --wf-cert PATH path of the server's public certificate (optional) + --wf-key PATH path of the server's private key (optional) + --wf-authenticator PATH path of authenticatior app (optional) + --wf-auth-header NAME name of the authentication header (optional) )"; } break; diff --git a/src/webfuse/ws/config.cpp b/src/webfuse/ws/config.cpp index 61bf44a..c3e3925 100644 --- a/src/webfuse/ws/config.cpp +++ b/src/webfuse/ws/config.cpp @@ -1,6 +1,8 @@ #include "webfuse/ws/config.hpp" #include "webfuse/util/commandline_reader.hpp" +#include +#include #include namespace @@ -87,6 +89,18 @@ ws_config::ws_config(int argc, char * argv[]) use_tls = true; } } + else if (arg == "--wf-authenticator") + { + get_arg(*this, reader, authenticator, "missing AUTHENTICATOR"); + } + else if (arg == "--wf-auth-header") + { + if (get_arg(*this, reader, auth_header, "missing AUTH_HEADER")) + { + std::transform(auth_header.begin(), auth_header.end(), auth_header.begin(), + [](auto c) {return std::tolower(c); }); + } + } else { args.push(arg.c_str()); diff --git a/src/webfuse/ws/config.hpp b/src/webfuse/ws/config.hpp index 6f09ffb..f884a5c 100644 --- a/src/webfuse/ws/config.hpp +++ b/src/webfuse/ws/config.hpp @@ -30,6 +30,9 @@ public: bool use_tls; std::string cert_path; std::string key_path; + + std::string authenticator; + std::string auth_header; }; } diff --git a/src/webfuse/ws/server.cpp b/src/webfuse/ws/server.cpp index 199e7ab..ee6ab53 100644 --- a/src/webfuse/ws/server.cpp +++ b/src/webfuse/ws/server.cpp @@ -1,4 +1,5 @@ #include "webfuse/ws/server.hpp" +#include "webfuse/util/authenticator.hpp" #include @@ -32,6 +33,9 @@ struct user_data uint32_t id = 0; std::queue requests; std::unordered_map> pending_responses; + + std::string authenticator; + std::string auth_header; }; @@ -72,6 +76,75 @@ void do_receive(void * in, int len, lws* wsi, user_data * data) } } +std::string get_auth_token_of_known_header(lws * wsi, lws_token_indexes header) +{ + std::string token; + int const length = lws_hdr_total_length(wsi, header); + if (length > 0) + { + std::vector data(length + 1); + int const actual_length = lws_hdr_copy(wsi, data.data(), length + 1, header); + if (actual_length > 0) + { + token = data.data(); + } + } + + return token; +} + +std::string get_auth_token_from_custom_header(lws * wsi, std::string const & auth_header) +{ + std::string token; + int const length = lws_hdr_custom_length(wsi, auth_header.c_str(), auth_header.size()); + if (length > 0) + { + std::vector data(length + 1); + int const actual_length = lws_hdr_custom_copy(wsi, data.data(), length + 1, + auth_header.c_str(), auth_header.size()); + if (actual_length > 0) + { + token = data.data(); + } + } + + return token; +} + +std::string get_auth_token(lws * wsi, std::string const & auth_header) +{ + if (auth_header == "authorization") + { + return get_auth_token_of_known_header(wsi, WSI_TOKEN_HTTP_AUTHORIZATION); + } + + if (auth_header == "x-auth-token") + { + return get_auth_token_of_known_header(wsi, WSI_TOKEN_X_AUTH_TOKEN); + } + + return get_auth_token_from_custom_header(wsi, auth_header); +} + +int do_authenticate(lws * wsi, std::string const & authenticator_app, std::string const & auth_header) +{ + int result = 0; + if ((!authenticator_app.empty()) && (!auth_header.empty())) + { + std::string token = get_auth_token(wsi, auth_header); + if (!token.empty()) + { + webfuse::authenticator authenticator(authenticator_app); + result = authenticator.authenticate(token) ? 0 : -1; + } + else + { + result = -1; + } + } + + return result; +} } @@ -90,6 +163,11 @@ static int ws_server_callback(struct lws *wsi, enum lws_callback_reasons reason, int result = 0; switch(reason) { + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + { + result = do_authenticate(wsi, data->authenticator, data->auth_header); + } + break; case LWS_CALLBACK_ESTABLISHED: if (nullptr == data->connection) { @@ -164,6 +242,9 @@ public: detail(ws_config const & config) : shutdown_requested(false) { + data.authenticator = config.authenticator; + data.auth_header = config.auth_header; + lws_set_log_level(0, nullptr); memset(reinterpret_cast(protocols), 0, sizeof(protocols)); diff --git a/test-src/integration/webfuse/test/process.cpp b/test-src/integration/webfuse/test/process.cpp index bd29c5c..b40b61e 100644 --- a/test-src/integration/webfuse/test/process.cpp +++ b/test-src/integration/webfuse/test/process.cpp @@ -80,7 +80,7 @@ int process::wait() { int status = 0; int rc = waitpid(pid, &status, 0); - if (rc == 0) + if (rc == pid) { exit_code = WEXITSTATUS(status); pid = 0;