diff --git a/README.md b/README.md index ada90eb..9c0196e 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,50 @@ Copy the built PAM module to `/lib/security/` or `/lib64/security/` as needed. ## Logging Rust backend logs to `/var/log/pam_rust_backend.log` by default. +## C / C++ Logging Wrappers (examples) + +This project exposes a simple Rust FFI sink and provides lightweight C and C++ wrappers for convenient logging without changing the Rust side. + +Headers: +- `pam-module/include/rust_backend_ffi.h` — canonical FFI declarations and level enum. +- `pam-module/include/rust_backend_logging_c.h` — C `printf`-style helpers. +- `pam-module/include/rust_backend_logging_cpp.h` — C++ `cout`/`cerr`-style RAII stream helpers. + +C example (printf-style): + +```c +#include "rust_backend_logging_c.h" + +// simple formatted info log +RUST_LOGF_INFO("user=%s result=%d", user, result); +``` + +C notes: +- Formatting uses a fixed buffer (`RUST_LOG_FMT_BUFSZ`, default 2048) and is performed in C; the formatted string is forwarded to Rust. +- If the formatted output is longer than the buffer it will be truncated; `vsnprintf` errors or a NULL format will log a small fallback message. + +C++ example (stream-style): + +```cpp +#include "rust_backend_ffi.h" + +// stream-style; message sent to Rust on destructor +RUST_COUT() << "user=" << user << " authenticated=" << (ok?"yes":"no") << std::flush; + +// error example +RUST_CERR() << "authentication failed for user=" << user << std::flush; +``` + +C++ notes: +- `RUST_COUT()` and `RUST_CERR()` are thin factories that return a `RustLogStream` object which buffers via `std::ostringstream` and sends the accumulated string to `rust_log_event_with_level` when the temporary is destroyed. +- The destructor is `noexcept` and swallow exceptions to avoid crossing FFI boundaries. + +Compatibility: +- The canonical ABI function is `rust_log_event_with_level(const char* event, RustLogLevel level)` and is implemented in Rust. +- A legacy `rust_log_event(const char* event)` wrapper remains and maps to `INFO` for backward compatibility. +- Wrappers are header-only and additive; no changes to the Rust backend are required. + + ## Safety - Rust panics are contained and never cross FFI. - C++ exceptions are caught before returning to PAM. diff --git a/pam-module/CMakeLists.txt b/pam-module/CMakeLists.txt index 6e40024..deec17b 100644 --- a/pam-module/CMakeLists.txt +++ b/pam-module/CMakeLists.txt @@ -23,3 +23,15 @@ target_link_libraries(pam_usercontainer PRIVATE ${RUST_STATICLIB}) # Install target install(TARGETS pam_usercontainer DESTINATION lib/security) + +# Examples: small programs demonstrating logging wrappers +set_source_files_properties(examples/logging_example.c PROPERTIES LANGUAGE CXX) +add_executable(logging_example_c examples/logging_example.c) +target_include_directories(logging_example_c PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) +add_dependencies(logging_example_c rust_backend_build) +target_link_libraries(logging_example_c PRIVATE ${RUST_STATICLIB}) + +add_executable(logging_example_cpp examples/logging_example.cpp) +target_include_directories(logging_example_cpp PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include) +add_dependencies(logging_example_cpp rust_backend_build) +target_link_libraries(logging_example_cpp PRIVATE ${RUST_STATICLIB}) diff --git a/pam-module/examples/logging_example.c b/pam-module/examples/logging_example.c new file mode 100644 index 0000000..61d4b8a --- /dev/null +++ b/pam-module/examples/logging_example.c @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: Apache-2.0 +// C example demonstrating printf-style Rust logging wrapper + +#include "../include/rust_backend_ffi.h" + +int main(int argc, char** argv) { + (void)argc; (void)argv; + // Ensure logger is initialized (nullptr -> default path) + rust_init_logging(NULL); + + const char* user = "alice"; + int result = 0; // pretend success + + RUST_LOGF_INFO("C example: user=%s authenticated=%d", user, result); + RUST_LOGF_DEBUG("C example: debug details: x=%d y=%d", 10, 20); + + return 0; +} diff --git a/pam-module/examples/logging_example.cpp b/pam-module/examples/logging_example.cpp new file mode 100644 index 0000000..ef02edd --- /dev/null +++ b/pam-module/examples/logging_example.cpp @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: Apache-2.0 +// C++ example demonstrating stream-style Rust logging wrapper + +#include "../include/rust_backend_ffi.h" +#include + +int main() { + rust_init_logging(nullptr); + + std::string user = "bob"; + bool ok = true; + + RUST_COUT() << "C++ example: user=" << user << " authenticated=" << (ok?"yes":"no") << std::flush; + RUST_CERR() << "C++ example: an error message for user=" << user << std::flush; + + return 0; +} diff --git a/pam-module/include/rust_backend_ffi.h b/pam-module/include/rust_backend_ffi.h index 96b99a7..6db29c3 100644 --- a/pam-module/include/rust_backend_ffi.h +++ b/pam-module/include/rust_backend_ffi.h @@ -44,3 +44,9 @@ void rust_log_event_with_level(const char* event, RustLogLevel level); #define RUST_LOG_DEBUG(msg) rust_log_event_with_level((msg), RUST_LOG_LEVEL_DEBUG) #define RUST_LOG_TRACE(msg) rust_log_event_with_level((msg), RUST_LOG_LEVEL_TRACE) +// C wrapper: printf-style helpers (header-only) +#include "rust_backend_logging_c.h" +#ifdef __cplusplus +#include "rust_backend_logging_cpp.h" +#endif + diff --git a/pam-module/include/rust_backend_logging_c.h b/pam-module/include/rust_backend_logging_c.h new file mode 100644 index 0000000..9b7253d --- /dev/null +++ b/pam-module/include/rust_backend_logging_c.h @@ -0,0 +1,74 @@ +// SPDX-License-Identifier: Apache-2.0 +// C printf-style wrappers for Rust logging FFI + +#pragma once + +#include +#include +#include +#include "rust_backend_ffi.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Buffer size for formatted messages. Documented truncation behavior. +#define RUST_LOG_FMT_BUFSZ 2048 + +static inline void rust_log_vprintf_level(RustLogLevel level, const char* fmt, va_list ap) { + if (fmt == NULL) { + rust_log_event_with_level("(null format)", level); + return; + } + char buf[RUST_LOG_FMT_BUFSZ]; + int ret = vsnprintf(buf, RUST_LOG_FMT_BUFSZ, fmt, ap); + if (ret < 0) { + rust_log_event_with_level("(format error)", level); + return; + } + // vsnprintf guarantees NUL-termination as long as size>0 + buf[RUST_LOG_FMT_BUFSZ - 1] = '\0'; + rust_log_event_with_level(buf, level); +} + +static inline void rust_log_printf_level(RustLogLevel level, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + rust_log_vprintf_level(level, fmt, ap); + va_end(ap); +} + +// Convenience per-level functions +static inline void rust_logf_fatal(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_FATAL, fmt, ap); va_end(ap); +} +static inline void rust_logf_error(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_ERROR, fmt, ap); va_end(ap); +} +static inline void rust_logf_warn(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_WARN, fmt, ap); va_end(ap); +} +static inline void rust_logf_info(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_INFO, fmt, ap); va_end(ap); +} +static inline void rust_logf_debug(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_DEBUG, fmt, ap); va_end(ap); +} +static inline void rust_logf_trace(const char* fmt, ...) { + va_list ap; va_start(ap, fmt); rust_log_vprintf_level(RUST_LOG_LEVEL_TRACE, fmt, ap); va_end(ap); +} + +// Convenience macros for C/C++ callers (variadic macro) +// Support both C99 and C++ compilers that support variadic macros +#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) + #define RUST_LOGF_FATAL(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_FATAL, (fmt), ##__VA_ARGS__) + #define RUST_LOGF_ERROR(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_ERROR, (fmt), ##__VA_ARGS__) + #define RUST_LOGF_WARN(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_WARN, (fmt), ##__VA_ARGS__) + #define RUST_LOGF_INFO(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_INFO, (fmt), ##__VA_ARGS__) + #define RUST_LOGF_DEBUG(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_DEBUG, (fmt), ##__VA_ARGS__) + #define RUST_LOGF_TRACE(fmt, ...) rust_log_printf_level(RUST_LOG_LEVEL_TRACE, (fmt), ##__VA_ARGS__) +#endif + +#ifdef __cplusplus +} +#endif diff --git a/pam-module/include/rust_backend_logging_cpp.h b/pam-module/include/rust_backend_logging_cpp.h new file mode 100644 index 0000000..53506f3 --- /dev/null +++ b/pam-module/include/rust_backend_logging_cpp.h @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: Apache-2.0 +// C++ stream-style wrappers for Rust logging FFI + +#pragma once + +// This header expects `rust_backend_ffi.h` to have been included first +// so that `RustLogLevel` and `rust_log_event_with_level` are available. + +#include +#include +#include + +class RustLogStream { +public: + explicit RustLogStream(RustLogLevel level) noexcept : level_(level) {} + + // Generic stream insertion + template + RustLogStream& operator<<(const T& v) { + try { + oss_ << v; + } catch (...) { + // swallow; avoid exceptions crossing FFI + } + return *this; + } + + // Support manipulators like std::endl + RustLogStream& operator<<(std::ostream& (*manip)(std::ostream&)) { + try { + manip(oss_); + } catch (...) { + } + return *this; + } + + // Destructor emits buffered message + ~RustLogStream() noexcept { + try { + const std::string s = oss_.str(); + if (!s.empty()) { + rust_log_event_with_level(s.c_str(), level_); + } + } catch (...) { + // never throw + } + } + + // Non-copyable + RustLogStream(const RustLogStream&) = delete; + RustLogStream& operator=(const RustLogStream&) = delete; + + // Movable + RustLogStream(RustLogStream&&) = default; + RustLogStream& operator=(RustLogStream&&) = default; + +private: + RustLogLevel level_; + std::ostringstream oss_; +}; + +// Factories and macros +inline RustLogStream rust_log_info_stream() { return RustLogStream(RUST_LOG_LEVEL_INFO); } +inline RustLogStream rust_log_warn_stream() { return RustLogStream(RUST_LOG_LEVEL_WARN); } +inline RustLogStream rust_log_error_stream() { return RustLogStream(RUST_LOG_LEVEL_ERROR); } +inline RustLogStream rust_log_debug_stream() { return RustLogStream(RUST_LOG_LEVEL_DEBUG); } +inline RustLogStream rust_log_trace_stream() { return RustLogStream(RUST_LOG_LEVEL_TRACE); } +inline RustLogStream rust_log_fatal_stream() { return RustLogStream(RUST_LOG_LEVEL_FATAL); } + +#define RUST_COUT() rust_log_info_stream() +#define RUST_CERR() rust_log_error_stream() +#define RUST_CDEBUG() rust_log_debug_stream() + +// Usage example: +// RUST_COUT() << "User=" << user << " logged in" << std::endl; diff --git a/pam-module/src/pam_module.cpp b/pam-module/src/pam_module.cpp index 98db6a3..00ff1bb 100644 --- a/pam-module/src/pam_module.cpp +++ b/pam-module/src/pam_module.cpp @@ -15,7 +15,7 @@ extern "C" { int pam_sm_authenticate(pam_handle_t* pamh, int flags, int argc, const char** argv) { try { rust_init_logging(nullptr); // Ensure logger is initialized - RUST_LOG_INFO("PAM authentication attempt"); + RUST_CDEBUG() << "PAM authentication attempt" << std::flush; const char* user = nullptr; diff --git a/rust-backend/src/lib.rs b/rust-backend/src/lib.rs index ddb5ace..b2be75e 100644 --- a/rust-backend/src/lib.rs +++ b/rust-backend/src/lib.rs @@ -7,6 +7,7 @@ use std::ffi::CStr; use std::os::raw::c_char; use std::panic::{catch_unwind, AssertUnwindSafe}; +use std::env; mod auth; mod logging; @@ -15,14 +16,19 @@ mod error; #[no_mangle] pub extern "C" fn rust_init_logging(log_path: *const c_char) { let _ = catch_unwind(AssertUnwindSafe(|| { - let path = unsafe { - if log_path.is_null() { - "/var/log/pam_rust_backend.log" - } else { - CStr::from_ptr(log_path).to_str().unwrap_or("/var/log/pam_rust_backend.log") + let env_path = env::var("PAM_RUST_LOG_PATH").ok().and_then(|s| if s.is_empty() { None } else { Some(s) }); + let path = if let Some(p) = env_path { + p + } else { + unsafe { + if log_path.is_null() { + "/var/log/pam_rust_backend.log".to_string() + } else { + CStr::from_ptr(log_path).to_str().unwrap_or("/var/log/pam_rust_backend.log").to_string() + } } }; - logging::init_logger(path); + logging::init_logger(&path); })); }