diff --git a/example/authenticator/pam/.gitignore b/example/authenticator/pam/.gitignore new file mode 100644 index 0000000..f3d6549 --- /dev/null +++ b/example/authenticator/pam/.gitignore @@ -0,0 +1 @@ +/build/ \ No newline at end of file diff --git a/example/authenticator/pam/CMakeLists.txt b/example/authenticator/pam/CMakeLists.txt new file mode 100644 index 0000000..6f46a4b --- /dev/null +++ b/example/authenticator/pam/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required(VERSION 3.10) +project(webfuse VERSION 2.0.0) + +option(WITHOUT_CLANG_TIDY "Disables clang tidy" OFF) + +set (CMAKE_CXX_STANDARD 17) + +find_library(PAM pam REQUIRED) +find_library(B64 b64 REQUIRED) + +add_executable(webfuse_pam_authenticator src/main.cpp) +target_link_libraries(webfuse_pam_authenticator pam b64) +install(TARGETS webfuse_pam_authenticator DESTINATION bin) +install(FILES etc/pam.d/webfuse DESTINATION /etc/pam.d) + +add_executable(webfuse_pam_token_encode src/token_encode.cpp) +target_link_libraries(webfuse_pam_token_encode b64) + +add_executable(webfuse_pam_token_decode src/token_decode.cpp) +target_link_libraries(webfuse_pam_token_decode b64) + +if(NOT(WITHOUT_CLANG_TIDY)) +set_property( + TARGET webfuse_pam_authenticator webfuse_pam_token_encode webfuse_pam_token_decode + PROPERTY CXX_CLANG_TIDY clang-tidy -checks=readability-*,-readability-identifier-length -warnings-as-errors=*) +endif() diff --git a/example/authenticator/pam/README.md b/example/authenticator/pam/README.md new file mode 100644 index 0000000..b89da3e --- /dev/null +++ b/example/authenticator/pam/README.md @@ -0,0 +1,31 @@ +# webfuse PAM authenticator + +This directory contains an example of a webfuse authenticator using PAM. + +The authenticator uses `username` and `password` for authentication. +Since webfuse only provides a token, username and password are encoded as follows: + + TOKEN := base64 ( USERNAME ":" PASSWORD ) + +Example: + + USERNAME := "user" + PASSWORD := "secret" + TOKEN := base64 ( "user:secret" ) = "XNlcjpzZWNyZXQ=" + +The utilities `webfuse_pam_token_encode` and `webfuse_pam_token_decode` can be used +to encode and decode tokens. + +## Build + + cmake -b build + cmake build + +## Dependencies + +- libpam +- libb64 + +## Notes + +- in order to make the authenticator work, read access to /etc/shadow is needed diff --git a/example/authenticator/pam/etc/pam.d/webfuse b/example/authenticator/pam/etc/pam.d/webfuse new file mode 100644 index 0000000..593775d --- /dev/null +++ b/example/authenticator/pam/etc/pam.d/webfuse @@ -0,0 +1,7 @@ +# Use unix authentication for webfuse authentication. +# +# NOTE: +# - in order to use unix authentication, read access to /etc/shadow is required + +auth required pam_unix.so +account required pam_unix.so diff --git a/example/authenticator/pam/src/main.cpp b/example/authenticator/pam/src/main.cpp new file mode 100644 index 0000000..aa9cb60 --- /dev/null +++ b/example/authenticator/pam/src/main.cpp @@ -0,0 +1,188 @@ +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace +{ + +bool decode(std::string const & token, std::string & username, std::string & password) +{ + std::istringstream encoded(token); + std::stringstream decoded; + + base64::decoder decoder; + decoder.decode(encoded, decoded); + auto const plain = decoded.str(); + auto const pos = plain.find(':'); + bool const success = (pos != std::string::npos); + if (success) + { + username = plain.substr(0, pos); + password = plain.substr(pos + 1); + } + + return success; +} + +struct credentials +{ + std::string username; + std::string password; +}; + +extern "C" int conversation( + int count, + struct pam_message const ** messages, + struct pam_response ** ret_responses, + void * user_data) +{ + if (count <= 0) { return PAM_CONV_ERR; } + + int result = PAM_SUCCESS; + auto * responses = reinterpret_cast(malloc(count * sizeof(struct pam_response))); + auto * creds = reinterpret_cast(user_data); + + for(int i = 0; (PAM_SUCCESS == result) && (i < count); i++) + { + auto * response = &responses[i]; + auto const * message = messages[i]; + + response->resp_retcode = 0; + response->resp = nullptr; + + switch(message->msg_style) + { + case PAM_PROMPT_ECHO_ON: + response->resp = strdup(creds->username.c_str()); + break; + case PAM_PROMPT_ECHO_OFF: + response->resp = strdup(creds->password.c_str()); + break; + case PAM_TEXT_INFO: + break; + case PAM_ERROR_MSG: + break; + default: + free(responses); + result = PAM_CONV_ERR; + break; + } + } + + if (PAM_SUCCESS == result) + { + *ret_responses = responses; + } + + return result; +} + +bool authenticate(std::string const & username, std::string const & password) +{ + credentials creds; + creds.username = username; + creds.password = password; + + struct pam_conv conv; + conv.conv = &conversation; + conv.appdata_ptr = reinterpret_cast(&creds); + + pam_handle_t * handle = nullptr; + bool cleanup_handle = false; + bool result = true; + { + auto const rc = pam_start("webfuse", nullptr, &conv, &handle); + result = (PAM_SUCCESS == rc); + cleanup_handle = result; + if (!result) + { + syslog(LOG_AUTH, "failed to start PAM conversation"); + } + } + + if (result) + { + pam_set_item(handle, PAM_USER, reinterpret_cast(username.c_str())); + auto const rc = pam_authenticate(handle, PAM_DISALLOW_NULL_AUTHTOK); + result = (PAM_SUCCESS == rc); + } + + if (result) + { + auto const rc = pam_acct_mgmt(handle, PAM_DISALLOW_NULL_AUTHTOK); + result = (PAM_SUCCESS == rc); + } + + if (cleanup_handle) + { + pam_end(handle, 0); + } + + return result; +} + +} + +int main(int argc, char* argv[]) +{ + int exit_code = EXIT_FAILURE; + bool print_usage = true; + + if (argc == 2) + { + std::string const token = argv[1]; + if (("-h" != token) && ("--help" != token)) + { + print_usage = false; + + openlog("webfuse_pam_auth", 0, LOG_AUTH); + + std::string username; + std::string password; + auto const decode_valid = decode(token, username, password); + if (decode_valid) + { + auto const is_authenticated = authenticate(username, password); + if (is_authenticated) + { + syslog(LOG_AUTH, "authenticate user \"%s\"", username.c_str()); + exit_code = EXIT_SUCCESS; + } + else + { + syslog(LOG_AUTH, "failed to authenticate user \"%s\"", username.c_str()); + } + } + else + { + syslog(LOG_AUTH, "failed to decode authentication token"); + } + + closelog(); + } + } + + + if (print_usage) + { + std::cout << R"(webfuse_pam_authenticator, (c) 2023 Falk Werner +webfuse PAM authenticator + +Usage: + webfuse_pam_authenticator + +Arguments: + token used for authentication + token := base64( ":" ) +)"; + } + + return exit_code; +} \ No newline at end of file diff --git a/example/authenticator/pam/src/token_decode.cpp b/example/authenticator/pam/src/token_decode.cpp new file mode 100644 index 0000000..8fa38df --- /dev/null +++ b/example/authenticator/pam/src/token_decode.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include + +int main(int argc, char* argv[]) +{ + int exit_code = EXIT_FAILURE; + + if (argc == 2) + { + std::istringstream token(argv[1]); + + base64::decoder decoder; + std::stringstream plain; + decoder.decode(token, plain); + + auto const plain_str = plain.str(); + auto const pos = plain_str.find(':'); + if (pos != std::string::npos) + { + auto const username = plain_str.substr(0, pos); + auto const password = plain_str.substr(pos + 1); + + std::cout << "username: " << username << std::endl; + std::cout << "password: " << password << std::endl; + exit_code = EXIT_SUCCESS; + } + + if (exit_code != EXIT_SUCCESS) + { + std::cerr << "error: failed to decode" << std::endl; + } + } + else + { + std::cout << R"(webfuse_pam_token_encode, (c) 2023 Falk Werner + Encode token for webfuse PAM authenticator + + Usage: + webfuse_pam_token_encode +)"; + } + + return exit_code; +} \ No newline at end of file diff --git a/example/authenticator/pam/src/token_encode.cpp b/example/authenticator/pam/src/token_encode.cpp new file mode 100644 index 0000000..3b930fd --- /dev/null +++ b/example/authenticator/pam/src/token_encode.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include +#include + +int main(int argc, char* argv[]) +{ + if (argc == 3) + { + std::string const username = argv[1]; + std::string const password = argv[2]; + + base64::encoder encoder; + std::istringstream plain(username + ':' + password); + std::stringstream token; + encoder.encode(plain, token); + + std::cout << token.str(); + } + else + { + std::cout << R"(webfuse_pam_token_encode, (c) 2023 Falk Werner + Encode token for webfuse PAM authenticator + + Usage: + webfuse_pam_token_encode +)"; + } + + return EXIT_SUCCESS; +} \ No newline at end of file