This commit is contained in:
2025-07-26 10:10:52 +08:00
parent 9f83969584
commit 81c11d3f30
21 changed files with 1095 additions and 1 deletions

12
pam-http/Cargo.toml Normal file
View File

@@ -0,0 +1,12 @@
[package]
name = "pam-http"
version = "0.1.0"
authors = ["Anthony Nowell <anowell@gmail.com>"]
[lib]
name = "pam_http"
crate-type = ["cdylib"]
[dependencies]
pam-bindings = { path = "../pam/" }
reqwest = { version = "0.11.3", features = ["blocking"] }

12
pam-http/Justfile Normal file
View File

@@ -0,0 +1,12 @@
all:
cargo build
install:
@cargo build --release
sudo cp conf/http-auth /etc/pam.d/
sudo cp ../target/release/libpam_http.so /lib/security/pam_http.so
test:
@just install
gcc -o ../target/pam_test test.c -lpam -lpam_misc

35
pam-http/README.md Normal file
View File

@@ -0,0 +1,35 @@
pam-http
========
A PAM HTTP BasicAuth module built using pam-rs
# Prerequisites
You need some libraries before you build like libpam and libssl.
If you're going to build on Ubuntu, just run this:
```
sudo apt-get install -y build-essential libpam0g-dev libpam0g libssl-dev
```
# Building
Just use `cargo build`.
# Usage
You need to move the build product to a folder where PAM is looking for modules.
If you're using Ubuntu you can move `libpam_http.so` to `/lib/security`.
After doing so you need to make sure it has proper permissions: `sudo chmod 755 /lib/security/libpam_http.so`.
Then you can place a configuration file in `/etc/pam.d/`. It can look something like this:
```
auth sufficient libpam_http.so url=https://theserver.example.com/someendpoint
account sufficient libpam_http.so
```
Make sure the endpoint you're specifying can receive GET requests and supports
[HTTP Basic Authentication](https://en.wikipedia.org/wiki/Basic_access_authentication#Client_side).
If the user is authenticated successfully it should return HTTP 200.

2
pam-http/conf/http-auth Normal file
View File

@@ -0,0 +1,2 @@
auth sufficient pam_http.so url=http://localhost:3000
account sufficient pam_http.so

88
pam-http/src/lib.rs Normal file
View File

@@ -0,0 +1,88 @@
extern crate pam;
extern crate reqwest;
use pam::constants::{PamFlag, PamResultCode, PAM_PROMPT_ECHO_OFF};
use pam::conv::Conv;
use pam::module::{PamHandle, PamHooks};
use reqwest::blocking::Client;
use reqwest::StatusCode;
use std::collections::HashMap;
use std::ffi::CStr;
use std::time::Duration;
use pam::pam_try;
struct PamHttp;
pam::pam_hooks!(PamHttp);
impl PamHooks for PamHttp {
// This function performs the task of authenticating the user.
fn sm_authenticate(pamh: &mut PamHandle, args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {
println!("Let's auth over HTTP");
let args: Vec<_> = args
.iter()
.map(|s| s.to_string_lossy())
.collect();
let args: HashMap<&str, &str> = args
.iter()
.map(|s| {
let mut parts = s.splitn(2, '=');
(parts.next().unwrap(), parts.next().unwrap_or(""))
})
.collect();
let user = pam_try!(pamh.get_user(None));
let url: &str = match args.get("url") {
Some(url) => url,
None => return PamResultCode::PAM_AUTH_ERR,
};
let conv = match pamh.get_item::<Conv>() {
Ok(Some(conv)) => conv,
Ok(None) => {
unreachable!("No conv available");
}
Err(err) => {
println!("Couldn't get pam_conv");
return err;
}
};
let password = pam_try!(conv.send(PAM_PROMPT_ECHO_OFF, "Word, yo: "));
let password = match password {
Some(password) => Some(pam_try!(password.to_str(), PamResultCode::PAM_AUTH_ERR)),
None => None,
};
println!("Got a password {:?}", password);
let status = pam_try!(
get_url(url, &user, password),
PamResultCode::PAM_AUTH_ERR
);
if !status.is_success() {
println!("HTTP Error: {}", status);
return PamResultCode::PAM_AUTH_ERR;
}
PamResultCode::PAM_SUCCESS
}
fn sm_setcred(_pamh: &mut PamHandle, _args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {
println!("set credentials");
PamResultCode::PAM_SUCCESS
}
fn acct_mgmt(_pamh: &mut PamHandle, _args: Vec<&CStr>, _flags: PamFlag) -> PamResultCode {
println!("account management");
PamResultCode::PAM_SUCCESS
}
}
fn get_url(url: &str, user: &str, password: Option<&str>) -> reqwest::Result<StatusCode> {
let client = Client::builder().timeout(Duration::from_secs(15)).build()?;
client
.get(url)
.basic_auth(user, password)
.send()
.map(|r| r.status())
}

52
pam-http/test.c Normal file
View File

@@ -0,0 +1,52 @@
#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <stdio.h>
const struct pam_conv conv = {
misc_conv,
NULL
};
int main(int argc, char *argv[]) {
pam_handle_t* pamh = NULL;
int retval;
const char* user = "nobody";
if(argc != 2) {
printf("Usage: app [username]\n");
exit(1);
}
user = argv[1];
retval = pam_start("http-auth", user, &conv, &pamh);
// Are the credentials correct?
if (retval == PAM_SUCCESS) {
printf("Credentials accepted.\n");
retval = pam_authenticate(pamh, 0);
}
// Can the accound be used at this time?
if (retval == PAM_SUCCESS) {
printf("Account is valid.\n");
retval = pam_acct_mgmt(pamh, 0);
}
// Did everything work?
if (retval == PAM_SUCCESS) {
printf("Authenticated\n");
} else {
printf("Not Authenticated\n");
}
// close PAM (end session)
if (pam_end(pamh, retval) != PAM_SUCCESS) {
pamh = NULL;
printf("check_user: failed to release authenticator\n");
exit(1);
}
return retval == PAM_SUCCESS ? 0 : 1;
}