initial commit: WIP debugging PAM module
This commit is contained in:
2
.dockerignore
Normal file
2
.dockerignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/docker
|
||||||
|
/build
|
||||||
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
build/
|
||||||
|
rust-backend/target/
|
||||||
|
pam-module/*.so
|
||||||
|
*.swp
|
||||||
|
*.log
|
||||||
16
CMakeLists.txt
Normal file
16
CMakeLists.txt
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
project(usercontainer2 LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
|
add_subdirectory(pam-module)
|
||||||
|
|
||||||
|
# Rust backend build integration
|
||||||
|
add_custom_target(rust_backend_build
|
||||||
|
COMMAND cargo build --release --manifest-path=${CMAKE_SOURCE_DIR}/rust-backend/Cargo.toml --target-dir ${CMAKE_BINARY_DIR}/rust-backend/target
|
||||||
|
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}/rust-backend
|
||||||
|
)
|
||||||
|
|
||||||
|
add_subdirectory(tests/app)
|
||||||
200
LICENSE
Normal file
200
LICENSE
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
|
||||||
|
Apache License
|
||||||
|
Version 2.0, January 2004
|
||||||
|
http://www.apache.org/licenses/
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||||
|
|
||||||
|
1. Definitions.
|
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction,
|
||||||
|
and distribution as defined by Sections 1 through 9 of this document.
|
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by
|
||||||
|
the copyright owner that is granting the License.
|
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all
|
||||||
|
other entities that control, are controlled by, or are under common
|
||||||
|
control with that entity. For the purposes of this definition,
|
||||||
|
"control" means (i) the power, direct or indirect, to cause the
|
||||||
|
direction or management of such entity, whether by contract or
|
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity
|
||||||
|
exercising permissions granted by this License.
|
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications,
|
||||||
|
including but not limited to software source code, documentation
|
||||||
|
source, and configuration files.
|
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical
|
||||||
|
transformation or translation of a Source form, including but
|
||||||
|
not limited to compiled object code, generated documentation,
|
||||||
|
and conversions to other media types.
|
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or
|
||||||
|
Object form, made available under the License, as indicated by a
|
||||||
|
copyright notice that is included in or attached to the work
|
||||||
|
(an example is provided in the Appendix below).
|
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object
|
||||||
|
form, that is based on (or derived from) the Work and for which the
|
||||||
|
editorial revisions, annotations, elaborations, or other modifications
|
||||||
|
represent, as a whole, an original work of authorship. For the purposes
|
||||||
|
of this License, Derivative Works shall not include works that remain
|
||||||
|
separable from, or merely link (or bind by name) to the interfaces of,
|
||||||
|
the Work and Derivative Works thereof.
|
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including
|
||||||
|
the original version of the Work and any modifications or additions
|
||||||
|
to that Work or Derivative Works thereof, that is intentionally
|
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of
|
||||||
|
the copyright owner. For the purposes of this definition, "submitted"
|
||||||
|
means any form of electronic, verbal, or written communication sent
|
||||||
|
to the Licensor or its representatives, including but not limited to
|
||||||
|
communication on electronic mailing lists, source code control systems,
|
||||||
|
and issue tracking systems that are managed by, or on behalf of, the
|
||||||
|
Licensor for the purpose of discussing and improving the Work, but
|
||||||
|
excluding communication that is conspicuously marked or otherwise
|
||||||
|
designated in writing by the copyright owner as "Not a Contribution."
|
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||||
|
on behalf of whom a Contribution has been received by Licensor and
|
||||||
|
subsequently incorporated within the Work.
|
||||||
|
|
||||||
|
2. Grant of Copyright License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor
|
||||||
|
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
||||||
|
royalty-free, irrevocable copyright license to reproduce, prepare
|
||||||
|
Derivative Works of, publicly display, publicly perform, sublicense,
|
||||||
|
and distribute the Work and such Derivative Works in Source or Object
|
||||||
|
form.
|
||||||
|
|
||||||
|
3. Grant of Patent License.
|
||||||
|
|
||||||
|
Subject to the terms and conditions of this License, each Contributor
|
||||||
|
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
||||||
|
royalty-free, irrevocable (except as stated in this section) patent
|
||||||
|
license to make, have made, use, offer to sell, sell, import, and
|
||||||
|
otherwise transfer the Work, where such license applies only to those
|
||||||
|
patent claims licensable by such Contributor that are necessarily
|
||||||
|
infringed by their Contribution(s) alone or by combination of their
|
||||||
|
Contribution(s) with the Work to which such Contribution(s) was
|
||||||
|
submitted. If You institute patent litigation against any entity
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
the Work or a Contribution incorporated within the Work constitutes
|
||||||
|
direct or contributory patent infringement, then any patent licenses
|
||||||
|
granted to You under this License for that Work shall terminate as of
|
||||||
|
the date such litigation is filed.
|
||||||
|
|
||||||
|
4. Redistribution.
|
||||||
|
|
||||||
|
You may reproduce and distribute copies of the Work or Derivative
|
||||||
|
Works thereof in any medium, with or without modifications, and in
|
||||||
|
Source or Object form, provided that You meet the following conditions:
|
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or Derivative Works
|
||||||
|
a copy of this License; and
|
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices
|
||||||
|
stating that You changed the files; and
|
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works that
|
||||||
|
You distribute, all copyright, patent, trademark, and attribution
|
||||||
|
notices from the Source form of the Work, excluding those notices
|
||||||
|
that do not pertain to any part of the Derivative Works; and
|
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its
|
||||||
|
distribution, then any Derivative Works that You distribute must
|
||||||
|
include a readable copy of the attribution notices contained
|
||||||
|
within such NOTICE file, excluding those notices that do not
|
||||||
|
pertain to any part of the Derivative Works, in at least one of
|
||||||
|
the following places: within a NOTICE text file distributed as
|
||||||
|
part of the Derivative Works; within the Source form or
|
||||||
|
documentation, if provided with the Derivative Works; or, within
|
||||||
|
a display generated by the Derivative Works, if and wherever such
|
||||||
|
third-party notices appear. The contents of the NOTICE file are
|
||||||
|
for informational purposes only and do not modify the License.
|
||||||
|
You may add Your own attribution notices within Derivative Works
|
||||||
|
that You distribute, alongside or as an addendum to the NOTICE
|
||||||
|
text from the Work, provided that such additional attribution
|
||||||
|
notices cannot be construed as modifying the License.
|
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and
|
||||||
|
may provide additional or different license terms and conditions
|
||||||
|
for use, reproduction, or distribution of Your modifications, or
|
||||||
|
for any such Derivative Works as a whole, provided Your use,
|
||||||
|
reproduction, and distribution of the Work otherwise complies with
|
||||||
|
the conditions stated in this License.
|
||||||
|
|
||||||
|
5. Submission of Contributions.
|
||||||
|
|
||||||
|
Unless You explicitly state otherwise, any Contribution intentionally
|
||||||
|
submitted for inclusion in the Work by You to the Licensor shall be
|
||||||
|
under the terms and conditions of this License, without any additional
|
||||||
|
terms or conditions. Notwithstanding the above, nothing herein shall
|
||||||
|
supersede or modify the terms of any separate license agreement you
|
||||||
|
may have executed with Licensor regarding such Contributions.
|
||||||
|
|
||||||
|
6. Trademarks.
|
||||||
|
|
||||||
|
This License does not grant permission to use the trade names,
|
||||||
|
trademarks, service marks, or product names of the Licensor, except as
|
||||||
|
required for describing the origin of the Work and reproducing the
|
||||||
|
content of the NOTICE file.
|
||||||
|
|
||||||
|
7. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, Licensor
|
||||||
|
provides the Work (and each Contributor provides its Contributions)
|
||||||
|
on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
||||||
|
either express or implied, including, without limitation, any
|
||||||
|
warranties or conditions of title, non-infringement, merchantability,
|
||||||
|
or fitness for a particular purpose. You are solely responsible for
|
||||||
|
determining the appropriateness of using or redistributing the Work
|
||||||
|
and assume any risks associated with Your exercise of permissions
|
||||||
|
under this License.
|
||||||
|
|
||||||
|
8. Limitation of Liability.
|
||||||
|
|
||||||
|
In no event and under no legal theory, whether in tort (including
|
||||||
|
negligence), contract, or otherwise, unless required by applicable
|
||||||
|
law (such as deliberate and grossly negligent acts) or agreed to in
|
||||||
|
writing, shall any Contributor be liable to You for damages, including
|
||||||
|
any direct, indirect, special, incidental, or consequential damages of
|
||||||
|
any character arising as a result of this License or out of the use or
|
||||||
|
inability to use the Work (including but not limited to damages for
|
||||||
|
loss of goodwill, work stoppage, computer failure or malfunction, or
|
||||||
|
any and all other commercial damages or losses), even if such
|
||||||
|
Contributor has been advised of the possibility of such damages.
|
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability.
|
||||||
|
|
||||||
|
While redistributing the Work or Derivative Works thereof, You may
|
||||||
|
choose to offer, and charge a fee for, acceptance of support,
|
||||||
|
warranty, indemnity, or other liability obligations and/or rights
|
||||||
|
consistent with this License. However, in accepting such obligations,
|
||||||
|
You may act only on Your own behalf and on Your sole responsibility,
|
||||||
|
not on behalf of any other Contributor, and only if You agree to
|
||||||
|
indemnify, defend, and hold each Contributor harmless for any
|
||||||
|
liability incurred by, or claims asserted against, such Contributor
|
||||||
|
by reason of your accepting any such warranty or additional liability.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
Copyright 2026 yono
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
82
README.md
Normal file
82
README.md
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
# PAM C++ + Rust Backend Monorepo
|
||||||
|
|
||||||
|
This project provides a Linux PAM module written in C++ (GNU g++, C++17) that delegates authentication and logging to a Rust static library backend. The build is orchestrated by CMake, which triggers Cargo for the Rust backend.
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
- `pam-module/`: C++ PAM module source
|
||||||
|
- `rust-backend/`: Rust static library backend
|
||||||
|
- `tests/`: Integration tests
|
||||||
|
|
||||||
|
## Build Requirements
|
||||||
|
- GNU g++ (C++17)
|
||||||
|
- CMake >= 3.15
|
||||||
|
- Rust (cargo)
|
||||||
|
- PAM development headers
|
||||||
|
|
||||||
|
## Build Instructions
|
||||||
|
```bash
|
||||||
|
mkdir build && cd build
|
||||||
|
cmake ..
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Safety
|
||||||
|
- Rust panics are contained and never cross FFI.
|
||||||
|
- C++ exceptions are caught before returning to PAM.
|
||||||
|
|
||||||
|
## Extending
|
||||||
|
Add new subprojects as needed for future business logic or integrations.
|
||||||
|
|
||||||
|
## Test Application (PAM Client)
|
||||||
|
|
||||||
|
This repository includes a test PAM client at `tests/pam_test_app.cpp`.
|
||||||
|
|
||||||
|
### Build the test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mkdir -p build && cd build
|
||||||
|
cmake ..
|
||||||
|
cmake --build .
|
||||||
|
```
|
||||||
|
|
||||||
|
The executable will be generated at `build/tests/pam_test_app`.
|
||||||
|
|
||||||
|
### Copy the PAM module
|
||||||
|
|
||||||
|
After building, copy the PAM module to the system PAM module path:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo cp build/pam-module/pam_module.so /lib/security/
|
||||||
|
```
|
||||||
|
|
||||||
|
On some distributions, use `/lib64/security/` instead.
|
||||||
|
|
||||||
|
### Configure `/etc/pam.d` service
|
||||||
|
|
||||||
|
Create `/etc/pam.d/pam_test_app` with:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
auth required pam_module.so
|
||||||
|
account required pam_permit.so
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also pass module arguments which are exposed as `argc` and `argv` to `pam_sm_authenticate`, for example:
|
||||||
|
|
||||||
|
```conf
|
||||||
|
auth required pam_module.so debug log_path=/var/log/pam_rust_backend.log
|
||||||
|
account required pam_permit.so
|
||||||
|
```
|
||||||
|
|
||||||
|
### Run the test client
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./tests/pam_test_app pam_test_app <user> <password>
|
||||||
|
```
|
||||||
|
|
||||||
|
The first argument (`pam_test_app`) must match the service filename in `/etc/pam.d/pam_test_app`.
|
||||||
21
docker/Dockerfile
Normal file
21
docker/Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM rust:1.94-trixie AS builder
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
SHELL [ "/bin/bash", "-o", "pipefail", "-c" ]
|
||||||
|
|
||||||
|
RUN apt update && apt install -y cmake build-essential libpam-dev && \
|
||||||
|
mkdir build && \
|
||||||
|
pushd build && \
|
||||||
|
cmake .. && \
|
||||||
|
cmake --build . && \
|
||||||
|
popd
|
||||||
|
|
||||||
|
FROM debian:trixie
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=builder --chmod=644 /app/build/pam-module/pam_usercontainer.so /lib/x86_64-linux-gnu/security/pam_usercontainer.so
|
||||||
|
COPY --from=builder /app/tests/config/pam.d/mytestservice /etc/pam.d/mytestservice
|
||||||
|
COPY --from=builder /app/build/tests/app/pam_test_app /usr/local/bin/pam_test_app
|
||||||
|
|
||||||
25
pam-module/CMakeLists.txt
Normal file
25
pam-module/CMakeLists.txt
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
project(pam_usercontainer LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||||
|
|
||||||
|
add_library(pam_usercontainer SHARED src/pam_module.cpp src/auth_client.cpp)
|
||||||
|
set_target_properties(pam_usercontainer PROPERTIES
|
||||||
|
OUTPUT_NAME "pam_usercontainer"
|
||||||
|
PREFIX ""
|
||||||
|
)
|
||||||
|
target_compile_options(pam_usercontainer PRIVATE
|
||||||
|
$<$<CXX_COMPILER_ID:GNU>:-Wall -Wextra>
|
||||||
|
$<$<CXX_COMPILER_ID:Clang>:-Wall -Wextra>
|
||||||
|
)
|
||||||
|
target_include_directories(pam_usercontainer PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
|
||||||
|
|
||||||
|
# Link to Rust staticlib
|
||||||
|
add_dependencies(pam_usercontainer rust_backend_build)
|
||||||
|
set(RUST_STATICLIB "${CMAKE_BINARY_DIR}/rust-backend/target/release/librust_backend.a")
|
||||||
|
target_link_libraries(pam_usercontainer PRIVATE ${RUST_STATICLIB})
|
||||||
|
|
||||||
|
# Install target
|
||||||
|
install(TARGETS pam_usercontainer DESTINATION lib/security)
|
||||||
24
pam-module/include/rust_backend_ffi.h
Normal file
24
pam-module/include/rust_backend_ffi.h
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Initialize logging
|
||||||
|
void rust_init_logging(const char* log_path);
|
||||||
|
|
||||||
|
// Authenticate user (stub)
|
||||||
|
int rust_auth_user(const char* user, const char* password);
|
||||||
|
|
||||||
|
// Log event (file sink)
|
||||||
|
void rust_log_event(const char* event);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
26
pam-module/src/auth_client.cpp
Normal file
26
pam-module/src/auth_client.cpp
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
#include "rust_backend_ffi.h"
|
||||||
|
#include "auth_client.h"
|
||||||
|
#include <exception>
|
||||||
|
// Add PAM header for status codes
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <security/pam_modules.h>
|
||||||
|
|
||||||
|
int auth_client_authenticate(const char* user, const char* password) {
|
||||||
|
try {
|
||||||
|
rust_init_logging(nullptr); // Ensure logger is initialized
|
||||||
|
int result = rust_auth_user(user, password);
|
||||||
|
if (result == 0) {
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
} else {
|
||||||
|
return PAM_AUTH_ERR;
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
return PAM_AUTH_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
7
pam-module/src/auth_client.h
Normal file
7
pam-module/src/auth_client.h
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
int auth_client_authenticate(const char* user, const char* password);
|
||||||
38
pam-module/src/pam_module.cpp
Normal file
38
pam-module/src/pam_module.cpp
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <security/pam_modules.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include "rust_backend_ffi.h"
|
||||||
|
#include "auth_client.h"
|
||||||
|
|
||||||
|
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_event("PAM authentication attempt");
|
||||||
|
|
||||||
|
|
||||||
|
const char* user = nullptr;
|
||||||
|
pam_get_user(pamh, &user, NULL);
|
||||||
|
const void* pw_ptr = nullptr;
|
||||||
|
int item_result = pam_get_item(pamh, PAM_AUTHTOK, &pw_ptr);
|
||||||
|
const char* password = (item_result == PAM_SUCCESS && pw_ptr) ? static_cast<const char*>(pw_ptr) : nullptr;
|
||||||
|
int result = auth_client_authenticate(user, password);
|
||||||
|
return result;
|
||||||
|
} catch (...) {
|
||||||
|
// Log and return PAM error
|
||||||
|
return PAM_AUTH_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int pam_sm_setcred(pam_handle_t*, int, int, const char**) {
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
16
rust-backend/Cargo.lock
generated
Normal file
16
rust-backend/Cargo.lock
generated
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# This file is automatically @generated by Cargo.
|
||||||
|
# It is not intended for manual editing.
|
||||||
|
version = 4
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "once_cell"
|
||||||
|
version = "1.21.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rust-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"once_cell",
|
||||||
|
]
|
||||||
16
rust-backend/Cargo.toml
Normal file
16
rust-backend/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[package]
|
||||||
|
name = "rust-backend"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["staticlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
once_cell = "1.18"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "unwind"
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "unwind"
|
||||||
14
rust-backend/src/auth.rs
Normal file
14
rust-backend/src/auth.rs
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
pub fn authenticate(user: &str, password: &str) -> Result<(), ()> {
|
||||||
|
// Stub: always fail if user == "fail", else succeed
|
||||||
|
if user == "fail" {
|
||||||
|
Err(())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
10
rust-backend/src/error.rs
Normal file
10
rust-backend/src/error.rs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
pub const SUCCESS: i32 = 0;
|
||||||
|
pub const ERR_INVALID_INPUT: i32 = -1;
|
||||||
|
pub const ERR_AUTH_FAILED: i32 = -2;
|
||||||
|
pub const ERR_PANIC: i32 = -99;
|
||||||
62
rust-backend/src/lib.rs
Normal file
62
rust-backend/src/lib.rs
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
use std::ffi::{CStr, CString};
|
||||||
|
use std::os::raw::c_char;
|
||||||
|
use std::panic::{catch_unwind, AssertUnwindSafe};
|
||||||
|
|
||||||
|
mod auth;
|
||||||
|
mod logging;
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
logging::init_logger(path);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rust_log_event(event: *const c_char) {
|
||||||
|
let _ = catch_unwind(AssertUnwindSafe(|| {
|
||||||
|
let msg = unsafe {
|
||||||
|
if event.is_null() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
CStr::from_ptr(event).to_str().unwrap_or("")
|
||||||
|
};
|
||||||
|
logging::log_event(msg);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn rust_auth_user(user: *const c_char, password: *const c_char) -> i32 {
|
||||||
|
catch_unwind(AssertUnwindSafe(|| {
|
||||||
|
let u = unsafe {
|
||||||
|
if user.is_null() {
|
||||||
|
return error::ERR_INVALID_INPUT;
|
||||||
|
}
|
||||||
|
CStr::from_ptr(user).to_str().unwrap_or("")
|
||||||
|
};
|
||||||
|
let p = unsafe {
|
||||||
|
if password.is_null() {
|
||||||
|
return error::ERR_INVALID_INPUT;
|
||||||
|
}
|
||||||
|
CStr::from_ptr(password).to_str().unwrap_or("")
|
||||||
|
};
|
||||||
|
match auth::authenticate(u, p) {
|
||||||
|
Ok(_) => error::SUCCESS,
|
||||||
|
Err(_) => error::ERR_AUTH_FAILED,
|
||||||
|
}
|
||||||
|
})).unwrap_or(error::ERR_PANIC)
|
||||||
|
}
|
||||||
32
rust-backend/src/logging.rs
Normal file
32
rust-backend/src/logging.rs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
use std::fs::{OpenOptions, File};
|
||||||
|
use std::io::Write;
|
||||||
|
use std::sync::Mutex;
|
||||||
|
use once_cell::sync::OnceCell;
|
||||||
|
|
||||||
|
static LOGGER: OnceCell<Mutex<File>> = OnceCell::new();
|
||||||
|
|
||||||
|
pub fn init_logger(path: &str) {
|
||||||
|
if LOGGER.get().is_some() {
|
||||||
|
// Already initialized, do nothing
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let file = OpenOptions::new()
|
||||||
|
.create(true)
|
||||||
|
.append(true)
|
||||||
|
.open(path)
|
||||||
|
.expect("Failed to open log file");
|
||||||
|
LOGGER.set(Mutex::new(file)).ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn log_event(event: &str) {
|
||||||
|
if let Some(logger) = LOGGER.get() {
|
||||||
|
let mut file = logger.lock().unwrap();
|
||||||
|
writeln!(file, "{}", event).ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
7
tests/app/CMakeLists.txt
Normal file
7
tests/app/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.15)
|
||||||
|
project(pam_test_app LANGUAGES CXX)
|
||||||
|
|
||||||
|
add_executable(pam_test_app pam_test_app.cpp)
|
||||||
|
target_link_libraries(pam_test_app PRIVATE pam)
|
||||||
|
|
||||||
|
install(TARGETS pam_test_app RUNTIME DESTINATION bin)
|
||||||
95
tests/app/pam_test_app.cpp
Normal file
95
tests/app/pam_test_app.cpp
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
// SPDX-License-Identifier: Apache-2.0
|
||||||
|
// Copyright 2026 Suyono
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
#include <security/pam_appl.h>
|
||||||
|
#include <security/pam_misc.h>
|
||||||
|
|
||||||
|
#include <cstdlib>
|
||||||
|
#include <cstring>
|
||||||
|
#include <iostream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
struct AppData {
|
||||||
|
const char* password;
|
||||||
|
};
|
||||||
|
|
||||||
|
int conversation(int num_msg, const struct pam_message** msg, struct pam_response** resp, void* appdata_ptr) {
|
||||||
|
if (num_msg <= 0 || msg == nullptr || resp == nullptr) {
|
||||||
|
return PAM_CONV_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* app_data = static_cast<AppData*>(appdata_ptr);
|
||||||
|
auto* replies = static_cast<pam_response*>(calloc(static_cast<size_t>(num_msg), sizeof(pam_response)));
|
||||||
|
if (replies == nullptr) {
|
||||||
|
return PAM_BUF_ERR;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < num_msg; ++i) {
|
||||||
|
replies[i].resp_retcode = 0;
|
||||||
|
replies[i].resp = nullptr;
|
||||||
|
|
||||||
|
switch (msg[i]->msg_style) {
|
||||||
|
case PAM_PROMPT_ECHO_OFF:
|
||||||
|
case PAM_PROMPT_ECHO_ON:
|
||||||
|
if (app_data != nullptr && app_data->password != nullptr) {
|
||||||
|
replies[i].resp = strdup(app_data->password);
|
||||||
|
if (replies[i].resp == nullptr) {
|
||||||
|
free(replies);
|
||||||
|
return PAM_BUF_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case PAM_ERROR_MSG:
|
||||||
|
case PAM_TEXT_INFO:
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
free(replies);
|
||||||
|
return PAM_CONV_ERR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*resp = replies;
|
||||||
|
return PAM_SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
if (argc < 3) {
|
||||||
|
std::cerr << "Usage: " << argv[0] << " <service> <user> [password]" << std::endl;
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string service = argv[1];
|
||||||
|
const std::string user = argv[2];
|
||||||
|
const char* password = (argc >= 4) ? argv[3] : "";
|
||||||
|
|
||||||
|
AppData app_data{password};
|
||||||
|
pam_conv conv{conversation, &app_data};
|
||||||
|
pam_handle_t* pamh = nullptr;
|
||||||
|
|
||||||
|
int rc = pam_start(service.c_str(), user.c_str(), &conv, &pamh);
|
||||||
|
if (rc != PAM_SUCCESS) {
|
||||||
|
std::cerr << "pam_start failed: " << pam_strerror(pamh, rc) << std::endl;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = pam_authenticate(pamh, 0);
|
||||||
|
if (rc == PAM_SUCCESS) {
|
||||||
|
std::cout << "Authentication succeeded" << std::endl;
|
||||||
|
} else {
|
||||||
|
std::cout << "Authentication failed: " << pam_strerror(pamh, rc) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int end_rc = pam_end(pamh, rc);
|
||||||
|
if (end_rc != PAM_SUCCESS) {
|
||||||
|
std::cerr << "pam_end failed: " << pam_strerror(pamh, end_rc) << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (rc == PAM_SUCCESS) ? 0 : 1;
|
||||||
|
}
|
||||||
1
tests/config/pam.d/mytestservice
Normal file
1
tests/config/pam.d/mytestservice
Normal file
@@ -0,0 +1 @@
|
|||||||
|
auth required pam_usercontainer.so
|
||||||
Reference in New Issue
Block a user