Fixed warnings, lots of stubs in the code, will be implemented later.
This commit is contained in:
164
CODING_STANDARDS.md
Normal file
164
CODING_STANDARDS.md
Normal file
@@ -0,0 +1,164 @@
|
||||
# Safety-Critical Coding Standards
|
||||
|
||||
This document outlines the safety-critical coding standards applied to Linux Hello, based on MISRA C and similar standards, adapted for both C and Rust code.
|
||||
|
||||
## Principles Applied
|
||||
|
||||
### 1. Simple Control Flow
|
||||
- ✅ **C**: No goto, setjmp, longjmp, or recursion
|
||||
- ✅ **Rust**: Avoid recursion where possible; use iterative solutions. Rust's ownership system prevents many control flow issues.
|
||||
|
||||
### 2. Fixed Loop Bounds
|
||||
- ✅ **C**: All loops have fixed upper bounds (e.g., `MAX_ARGS = 32`)
|
||||
- ✅ **Rust**: Use bounded iterators, `take()`, or explicit bounds. Example:
|
||||
```rust
|
||||
for item in items.iter().take(MAX_ITEMS) { ... }
|
||||
```
|
||||
|
||||
### 3. No Dynamic Memory After Init
|
||||
- ✅ **C**: No malloc/free after startup (stack allocation only)
|
||||
- ⚠️ **Rust**: Rust's allocator is safer, but we still prefer:
|
||||
- Pre-allocated buffers where possible
|
||||
- `Vec::with_capacity()` to avoid reallocations
|
||||
- Stack allocation for small data
|
||||
|
||||
### 4. Function Length Limit (~60 lines)
|
||||
- ✅ **C**: All functions under 60 lines
|
||||
- ✅ **Rust**: Same principle - break complex functions into smaller, testable units
|
||||
|
||||
### 5. Runtime Assertions (2+ per function)
|
||||
- ✅ **C**: Using `assert()` and `assert_condition()` helper
|
||||
- ✅ **Rust**: Use `debug_assert!()`, `assert!()`, and `unwrap_or_else()` with validation
|
||||
|
||||
### 6. Smallest Scope for Data
|
||||
- ✅ **C**: Declare variables at smallest possible scope
|
||||
- ✅ **Rust**: Rust enforces this - variables are scoped to blocks
|
||||
|
||||
### 7. Check All Return Values
|
||||
- ✅ **C**: All function returns checked, void casts for intentional ignores
|
||||
- ✅ **Rust**: Use `Result<T, E>` and `Option<T>` - compiler enforces checking
|
||||
|
||||
### 8. Minimal Preprocessor
|
||||
- ✅ **C**: Only `#include` and simple `#define` constants
|
||||
- ✅ **Rust**: Use `cfg!()` and `#[cfg(...)]` attributes instead of macros
|
||||
|
||||
### 9. Restricted Pointer Use
|
||||
- ✅ **C**: Single level dereferencing only, no function pointers unless essential
|
||||
- ✅ **Rust**: Rust's references are safer, but we still:
|
||||
- Avoid deep nesting (`&&&T`)
|
||||
- Prefer owned types over references where possible
|
||||
- Use function pointers only when necessary (e.g., trait objects)
|
||||
|
||||
### 10. Strictest Warnings
|
||||
- ✅ **C**: `-Wall -Wextra -Werror -Wpedantic` + static analysis
|
||||
- ✅ **Rust**: `#![deny(warnings)]` in critical modules, `clippy` with pedantic lints
|
||||
|
||||
## Implementation Status
|
||||
|
||||
### C PAM Module (`pam-module/pam_linux_hello.c`)
|
||||
✅ **Fully compliant**:
|
||||
- Split `authenticate_face()` into smaller functions
|
||||
- Fixed loop bounds (`MAX_ARGS = 32`)
|
||||
- All return values checked
|
||||
- Runtime assertions added
|
||||
- Strictest compiler warnings enabled
|
||||
- Single-level pointer dereferencing
|
||||
- No dynamic allocation after init
|
||||
|
||||
### Rust Codebase
|
||||
🔄 **In Progress** - Applying equivalent principles:
|
||||
|
||||
#### Completed:
|
||||
- ✅ Return values checked (Rust enforces this)
|
||||
- ✅ Small scope (Rust enforces this)
|
||||
- ✅ No goto/setjmp (not applicable in Rust)
|
||||
- ✅ Minimal preprocessor (Rust uses attributes)
|
||||
|
||||
#### To Improve:
|
||||
- ⏳ Function length limits (some functions > 60 lines)
|
||||
- ⏳ Fixed loop bounds (some iterators unbounded)
|
||||
- ⏳ More runtime assertions
|
||||
- ⏳ Enable strictest Rust lints
|
||||
|
||||
## Rust-Specific Safety Measures
|
||||
|
||||
### Memory Safety
|
||||
- Rust's ownership system prevents use-after-free, double-free
|
||||
- No manual memory management needed
|
||||
- `unsafe` blocks are minimal and documented
|
||||
|
||||
### Type Safety
|
||||
- Strong typing prevents many classes of bugs
|
||||
- Pattern matching ensures exhaustive handling
|
||||
- `Result` and `Option` enforce error handling
|
||||
|
||||
### Bounds Checking
|
||||
- Array/vector bounds checked at runtime (can be disabled with `unsafe`)
|
||||
- Iterator bounds enforced by type system
|
||||
|
||||
## Static Analysis
|
||||
|
||||
### C Code
|
||||
```bash
|
||||
# Compile with strictest warnings
|
||||
make CFLAGS="-Wall -Wextra -Werror -Wpedantic ..."
|
||||
|
||||
# Run static analysis
|
||||
cppcheck --enable=all pam-module/pam_linux_hello.c
|
||||
splint pam-module/pam_linux_hello.c
|
||||
```
|
||||
|
||||
### Rust Code
|
||||
```bash
|
||||
# Clippy with pedantic lints
|
||||
cargo clippy -- -W clippy::pedantic
|
||||
|
||||
# Deny warnings in release
|
||||
RUSTFLAGS="-D warnings" cargo build --release
|
||||
```
|
||||
|
||||
## Code Review Checklist
|
||||
|
||||
- [ ] No functions > 60 lines
|
||||
- [ ] All loops have fixed bounds
|
||||
- [ ] All return values checked
|
||||
- [ ] Minimum 2 assertions per function
|
||||
- [ ] Variables at smallest scope
|
||||
- [ ] No recursion (unless essential)
|
||||
- [ ] Single-level pointer/reference dereferencing
|
||||
- [ ] Compiles with strictest warnings
|
||||
- [ ] Passes static analysis
|
||||
|
||||
## Examples
|
||||
|
||||
### Good: Fixed Loop Bound
|
||||
```rust
|
||||
const MAX_ITEMS: usize = 100;
|
||||
for item in items.iter().take(MAX_ITEMS) {
|
||||
// Process item
|
||||
}
|
||||
```
|
||||
|
||||
### Good: Assertions
|
||||
```rust
|
||||
fn process_data(data: &[u8]) -> Result<()> {
|
||||
debug_assert!(!data.is_empty(), "Data must not be empty");
|
||||
debug_assert!(data.len() <= MAX_SIZE, "Data exceeds maximum size");
|
||||
// ... processing
|
||||
}
|
||||
```
|
||||
|
||||
### Good: Small Functions
|
||||
```rust
|
||||
// Instead of one 100-line function, split:
|
||||
fn validate_input(input: &str) -> Result<()> { ... } // 20 lines
|
||||
fn parse_input(input: &str) -> Result<Data> { ... } // 25 lines
|
||||
fn process_data(data: Data) -> Result<Output> { ... } // 30 lines
|
||||
```
|
||||
|
||||
## References
|
||||
|
||||
- MISRA C:2012 Guidelines
|
||||
- CERT C Coding Standard
|
||||
- Rust API Guidelines
|
||||
- Safety-Critical Software Development
|
||||
1
Cargo.lock
generated
1
Cargo.lock
generated
@@ -510,6 +510,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"clap",
|
||||
"image",
|
||||
"libc",
|
||||
"linux-hello-common",
|
||||
"linux-hello-daemon",
|
||||
"serde",
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img src="https://your-image-host.com/logo.png" alt="Linux Hello Logo" width="200">
|
||||
<img src="public/logo.png" alt="Linux Hello Logo" width="200">
|
||||
</div>
|
||||
|
||||
# Linux Hello
|
||||
@@ -139,6 +139,8 @@ Linux Hello provides secure facial authentication for Linux systems, similar to
|
||||
|
||||
## Installation
|
||||
|
||||
> **📖 New to testing?** See [TESTING.md](TESTING.md) for a detailed step-by-step testing guide.
|
||||
|
||||
### Building from Source
|
||||
|
||||
```bash
|
||||
|
||||
398
TESTING.md
Normal file
398
TESTING.md
Normal file
@@ -0,0 +1,398 @@
|
||||
# Testing Linux Hello on Your Linux Computer
|
||||
|
||||
This guide will help you test Linux Hello on your system. We'll start with safe CLI testing before any system-level installation.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Install Required Packages
|
||||
|
||||
**Debian/Ubuntu:**
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y \
|
||||
build-essential \
|
||||
libpam0g-dev \
|
||||
v4l-utils \
|
||||
pkg-config \
|
||||
libssl-dev
|
||||
```
|
||||
|
||||
**Fedora/RHEL:**
|
||||
```bash
|
||||
sudo dnf install -y \
|
||||
gcc \
|
||||
make \
|
||||
pam-devel \
|
||||
v4l-utils \
|
||||
pkgconfig \
|
||||
openssl-devel
|
||||
```
|
||||
|
||||
**Arch Linux:**
|
||||
```bash
|
||||
sudo pacman -S \
|
||||
base-devel \
|
||||
pam \
|
||||
v4l-utils \
|
||||
pkgconf \
|
||||
openssl
|
||||
```
|
||||
|
||||
### Install Rust
|
||||
|
||||
If you don't have Rust installed:
|
||||
```bash
|
||||
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||
source $HOME/.cargo/env
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Build the Project
|
||||
|
||||
```bash
|
||||
# Clone or navigate to the project directory
|
||||
cd Linux-Hello
|
||||
|
||||
# Build in release mode (faster, optimized)
|
||||
cargo build --release
|
||||
```
|
||||
|
||||
This will create:
|
||||
- `target/release/linux-hello` (CLI tool)
|
||||
- `target/release/linux-hello-daemon` (daemon)
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Test Without Installation (Safest)
|
||||
|
||||
You can test most features without installing anything system-wide.
|
||||
|
||||
### 2.1 Check Camera Detection
|
||||
|
||||
```bash
|
||||
# Run the CLI tool directly
|
||||
./target/release/linux-hello status --camera
|
||||
```
|
||||
|
||||
**Expected output:**
|
||||
- Lists available cameras
|
||||
- Shows IR camera detection
|
||||
- May show "No cameras found" if no camera is connected
|
||||
|
||||
**Note:** Works even without a camera (will show empty list or use mock).
|
||||
|
||||
### 2.2 Test Configuration
|
||||
|
||||
```bash
|
||||
# View default configuration
|
||||
./target/release/linux-hello config
|
||||
|
||||
# View as JSON
|
||||
./target/release/linux-hello config --json
|
||||
```
|
||||
|
||||
### 2.3 Capture Test Frames (if camera available)
|
||||
|
||||
```bash
|
||||
# Create a test directory
|
||||
mkdir -p ~/test-frames
|
||||
|
||||
# Capture 5 frames
|
||||
./target/release/linux-hello capture --output ~/test-frames --count 5
|
||||
```
|
||||
|
||||
**Expected:** Creates PNG files in `~/test-frames/` (if camera available).
|
||||
|
||||
### 2.4 Test Face Detection (with image file)
|
||||
|
||||
```bash
|
||||
# Test detection on a photo
|
||||
./target/release/linux-hello detect --image ~/test-frames/frame_000.png --output ~/detected.png
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- May detect a "face" (placeholder algorithm is very basic)
|
||||
- Creates annotated image if face detected
|
||||
- **Note:** Placeholder detection is unreliable - it just checks image statistics
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Test with Daemon (Manual Start)
|
||||
|
||||
Before installing system-wide, test the daemon manually:
|
||||
|
||||
### 3.1 Start Daemon Manually
|
||||
|
||||
```bash
|
||||
# Create runtime directory
|
||||
sudo mkdir -p /run/linux-hello
|
||||
sudo chmod 777 /run/linux-hello # For testing only!
|
||||
|
||||
# Start daemon in foreground (to see logs)
|
||||
sudo ./target/release/linux-hello-daemon
|
||||
```
|
||||
|
||||
**Keep this terminal open** - you'll see daemon logs.
|
||||
|
||||
### 3.2 Test Enrollment (in another terminal)
|
||||
|
||||
```bash
|
||||
# In a new terminal, test enrollment
|
||||
./target/release/linux-hello enroll --label test
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- Captures frames from camera
|
||||
- Creates face template
|
||||
- Stores in `/var/lib/linux-hello/templates/` (may need sudo)
|
||||
|
||||
**Note:** With placeholder algorithms, this will work but won't be accurate.
|
||||
|
||||
### 3.3 Test Authentication
|
||||
|
||||
```bash
|
||||
# Test authentication
|
||||
./target/release/linux-hello test
|
||||
```
|
||||
|
||||
**Expected:**
|
||||
- Attempts to authenticate using your face
|
||||
- May succeed or fail (placeholder algorithms are unreliable)
|
||||
|
||||
### 3.4 List Enrolled Templates
|
||||
|
||||
```bash
|
||||
./target/release/linux-hello list
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Full Installation (Optional - More Invasive)
|
||||
|
||||
⚠️ **Warning:** This modifies system PAM configuration. Test in a VM or have a recovery plan.
|
||||
|
||||
### 4.1 Build PAM Module
|
||||
|
||||
```bash
|
||||
cd pam-module
|
||||
make
|
||||
```
|
||||
|
||||
### 4.2 Install Components
|
||||
|
||||
```bash
|
||||
# Install PAM module
|
||||
sudo make install
|
||||
|
||||
# Install daemon
|
||||
sudo cp ../target/release/linux-hello-daemon /usr/libexec/
|
||||
sudo chmod +x /usr/libexec/linux-hello-daemon
|
||||
|
||||
# Install systemd service
|
||||
sudo cp ../dist/linux-hello.service /etc/systemd/system/
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable linux-hello.service
|
||||
sudo systemctl start linux-hello.service
|
||||
|
||||
# Install configuration
|
||||
sudo mkdir -p /etc/linux-hello
|
||||
sudo cp ../dist/config.toml /etc/linux-hello/
|
||||
```
|
||||
|
||||
### 4.3 Configure PAM (CAREFUL!)
|
||||
|
||||
**Backup first:**
|
||||
```bash
|
||||
sudo cp /etc/pam.d/common-auth /etc/pam.d/common-auth.backup
|
||||
```
|
||||
|
||||
**Edit PAM config:**
|
||||
```bash
|
||||
sudo nano /etc/pam.d/common-auth
|
||||
```
|
||||
|
||||
Add at the top:
|
||||
```
|
||||
auth sufficient pam_linux_hello.so debug fallback=password
|
||||
auth required pam_unix.so use_first_pass
|
||||
```
|
||||
|
||||
**Test login:**
|
||||
- Try logging out and back in
|
||||
- Face authentication will be attempted first
|
||||
- Falls back to password if it fails
|
||||
|
||||
---
|
||||
|
||||
## Step 5: What to Expect
|
||||
|
||||
### With Placeholder Algorithms
|
||||
|
||||
**Face Detection:**
|
||||
- Very basic - just checks image brightness/contrast
|
||||
- Assumes face is centered if image has "reasonable" contrast
|
||||
- **Not reliable** - may detect faces where there aren't any, or miss real faces
|
||||
|
||||
**Face Recognition:**
|
||||
- Uses image statistics (mean, variance, histogram)
|
||||
- **Cannot reliably distinguish between different people**
|
||||
- May authenticate the wrong person
|
||||
- May reject the correct person
|
||||
|
||||
**Anti-Spoofing:**
|
||||
- Framework is implemented
|
||||
- But depends on accurate face detection (which we don't have)
|
||||
- **Will not effectively prevent photo/video attacks**
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
✅ **Will work:**
|
||||
- Camera enumeration
|
||||
- Frame capture
|
||||
- Template storage/retrieval
|
||||
- CLI commands
|
||||
- Daemon communication
|
||||
- Basic workflow
|
||||
|
||||
❌ **Will NOT work reliably:**
|
||||
- Actual face recognition
|
||||
- Accurate authentication
|
||||
- Spoofing prevention
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Troubleshooting
|
||||
|
||||
### Daemon Won't Start
|
||||
|
||||
```bash
|
||||
# Check if socket directory exists
|
||||
ls -la /run/linux-hello/
|
||||
|
||||
# Check daemon logs
|
||||
sudo journalctl -u linux-hello.service -f
|
||||
```
|
||||
|
||||
### Camera Not Detected
|
||||
|
||||
```bash
|
||||
# List V4L2 devices
|
||||
v4l2-ctl --list-devices
|
||||
|
||||
# Check permissions
|
||||
ls -la /dev/video*
|
||||
|
||||
# May need to add user to video group
|
||||
sudo usermod -aG video $USER
|
||||
# Log out and back in
|
||||
```
|
||||
|
||||
### Permission Errors
|
||||
|
||||
```bash
|
||||
# Template storage directory
|
||||
sudo mkdir -p /var/lib/linux-hello/templates
|
||||
sudo chmod 755 /var/lib/linux-hello/templates
|
||||
|
||||
# Runtime directory
|
||||
sudo mkdir -p /run/linux-hello
|
||||
sudo chmod 755 /run/linux-hello
|
||||
```
|
||||
|
||||
### PAM Module Issues
|
||||
|
||||
```bash
|
||||
# Check if module is installed
|
||||
ls -la /lib/x86_64-linux-gnu/security/pam_linux_hello.so
|
||||
|
||||
# Test PAM module manually
|
||||
sudo pam_test -a pam_linux_hello.so
|
||||
|
||||
# Check PAM logs
|
||||
sudo tail -f /var/log/auth.log
|
||||
# or
|
||||
sudo journalctl -f | grep pam
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Running Tests
|
||||
|
||||
Test the codebase itself:
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
cargo test --workspace
|
||||
|
||||
# Run specific test suites
|
||||
cargo test --test phase3_security_test
|
||||
cargo test --test phase2_auth_test
|
||||
|
||||
# Run with output
|
||||
cargo test --workspace -- --nocapture
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Cleanup (If Needed)
|
||||
|
||||
### Remove Installation
|
||||
|
||||
```bash
|
||||
# Stop and disable service
|
||||
sudo systemctl stop linux-hello.service
|
||||
sudo systemctl disable linux-hello.service
|
||||
|
||||
# Remove PAM module
|
||||
cd pam-module
|
||||
sudo make uninstall
|
||||
|
||||
# Remove daemon
|
||||
sudo rm /usr/libexec/linux-hello-daemon
|
||||
|
||||
# Remove service file
|
||||
sudo rm /etc/systemd/system/linux-hello.service
|
||||
sudo systemctl daemon-reload
|
||||
|
||||
# Restore PAM config
|
||||
sudo cp /etc/pam.d/common-auth.backup /etc/pam.d/common-auth
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Safety Recommendations
|
||||
|
||||
1. **Test in a VM first** - Don't test on your main system
|
||||
2. **Keep password fallback** - Always have `fallback=password` in PAM config
|
||||
3. **Backup PAM configs** - Before modifying `/etc/pam.d/`
|
||||
4. **Test logout/login** - In a separate session (don't lock yourself out)
|
||||
5. **Don't use for real security** - Placeholder algorithms are not secure
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
Once you've tested the basic functionality:
|
||||
|
||||
1. **Wait for ONNX models** - Real face recognition requires ML models
|
||||
2. **Get TPM2 hardware** - For secure template storage
|
||||
3. **Security audit** - Before production use
|
||||
4. **Contribute** - Help improve the project!
|
||||
|
||||
---
|
||||
|
||||
## Quick Test Checklist
|
||||
|
||||
- [ ] Project builds successfully
|
||||
- [ ] CLI tool runs (`./target/release/linux-hello status`)
|
||||
- [ ] Camera enumeration works (or shows empty list)
|
||||
- [ ] Configuration displays correctly
|
||||
- [ ] Daemon starts manually
|
||||
- [ ] Enrollment works (creates template)
|
||||
- [ ] Authentication test runs (may not be accurate)
|
||||
- [ ] Tests pass (`cargo test --workspace`)
|
||||
|
||||
---
|
||||
|
||||
**Remember:** This is development software with placeholder algorithms. It demonstrates the architecture but is NOT suitable for production use until ONNX models are integrated.
|
||||
@@ -22,3 +22,4 @@ tracing-subscriber.workspace = true
|
||||
tokio.workspace = true
|
||||
clap.workspace = true
|
||||
image.workspace = true
|
||||
libc = "0.2"
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
use clap::{Parser, Subcommand};
|
||||
use linux_hello_common::{Config, Result};
|
||||
use tracing::{info, Level};
|
||||
use tracing::{info, warn, Level};
|
||||
use tracing_subscriber::FmtSubscriber;
|
||||
|
||||
#[derive(Parser)]
|
||||
@@ -105,6 +105,26 @@ enum Commands {
|
||||
},
|
||||
}
|
||||
|
||||
/// Get the real username (handles sudo case)
|
||||
fn get_real_username() -> String {
|
||||
// Try SUDO_USER first (set when running with sudo)
|
||||
// This is the most reliable way to get the real user when using sudo
|
||||
std::env::var("SUDO_USER")
|
||||
.or_else(|_| std::env::var("USER"))
|
||||
.or_else(|_| std::env::var("USERNAME"))
|
||||
.unwrap_or_else(|_| {
|
||||
// Final fallback: try to get from whoami command
|
||||
std::process::Command::new("whoami")
|
||||
.output()
|
||||
.ok()
|
||||
.and_then(|output| {
|
||||
String::from_utf8(output.stdout).ok()
|
||||
})
|
||||
.map(|s| s.trim().to_string())
|
||||
.unwrap_or_else(|| "unknown".to_string())
|
||||
})
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let cli = Cli::parse();
|
||||
@@ -212,6 +232,20 @@ async fn cmd_capture(
|
||||
info!("Saved frame {} (converted from YUYV to grayscale)", filename);
|
||||
}
|
||||
}
|
||||
PixelFormat::Mjpeg => {
|
||||
// Decode MJPEG and save as PNG
|
||||
let filename = format!("{}/frame_{:03}.png", output, i);
|
||||
match image::load_from_memory(&frame.data) {
|
||||
Ok(img) => {
|
||||
let gray = img.to_luma8();
|
||||
gray.save(&filename).map_err(|e| linux_hello_common::Error::Io(std::io::Error::new(std::io::ErrorKind::Other, e)))?;
|
||||
info!("Saved frame {} (decoded from MJPEG)", filename);
|
||||
}
|
||||
Err(e) => {
|
||||
warn!("Failed to decode MJPEG frame {}: {}", i, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let filename = format!("{}/frame_{:03}.raw", output, i);
|
||||
std::fs::write(&filename, &frame.data)?;
|
||||
@@ -402,10 +436,8 @@ async fn cmd_enroll(config: &Config, label: &str) -> Result<()> {
|
||||
|
||||
info!("Starting enrollment with label: {}", label);
|
||||
|
||||
// Get current user
|
||||
let user = std::env::var("USER")
|
||||
.or_else(|_| std::env::var("USERNAME"))
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
// Get real user (handles sudo)
|
||||
let user = get_real_username();
|
||||
|
||||
println!("Enrolling user: {}", user);
|
||||
println!("Label: {}", label);
|
||||
@@ -490,10 +522,8 @@ async fn cmd_remove(_config: &Config, label: Option<&str>, all: bool) -> Result<
|
||||
async fn cmd_test(config: &Config, verbose: bool, _debug: bool) -> Result<()> {
|
||||
use linux_hello_daemon::auth::AuthService;
|
||||
|
||||
// Get current user
|
||||
let user = std::env::var("USER")
|
||||
.or_else(|_| std::env::var("USERNAME"))
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
// Get real user (handles sudo)
|
||||
let user = get_real_username();
|
||||
|
||||
println!("Testing authentication for user: {}", user);
|
||||
println!("Please look at the camera...");
|
||||
|
||||
@@ -15,9 +15,9 @@
|
||||
//! Each method returns a score between 0.0 (definitely fake) and 1.0
|
||||
//! (definitely real). Scores are combined using weighted averaging.
|
||||
|
||||
use linux_hello_common::{Error, Result};
|
||||
use linux_hello_common::Result;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::{debug, info, warn};
|
||||
use tracing::debug;
|
||||
|
||||
/// Minimum liveness score to pass authentication
|
||||
pub const DEFAULT_LIVENESS_THRESHOLD: f32 = 0.7;
|
||||
@@ -108,6 +108,7 @@ pub struct AntiSpoofingDetector {
|
||||
|
||||
/// Extracted features from a frame for temporal analysis
|
||||
#[derive(Clone)]
|
||||
#[allow(dead_code)] // Fields stored for future temporal analysis features
|
||||
struct FrameFeatures {
|
||||
/// Average brightness in face region
|
||||
brightness: f32,
|
||||
|
||||
@@ -178,6 +178,14 @@ impl AuthService {
|
||||
"Failed to create grayscale from YUYV".to_string(),
|
||||
))?
|
||||
}
|
||||
PixelFormat::Mjpeg => {
|
||||
// Decode MJPEG (JPEG) to image, then convert to grayscale
|
||||
image::load_from_memory(&frame.data)
|
||||
.map_err(|e| linux_hello_common::Error::Detection(
|
||||
format!("Failed to decode MJPEG: {}", e)
|
||||
))?
|
||||
.to_luma8()
|
||||
}
|
||||
_ => {
|
||||
return Err(linux_hello_common::Error::Detection(format!(
|
||||
"Unsupported pixel format: {:?}",
|
||||
|
||||
@@ -5,15 +5,23 @@
|
||||
use linux_hello_common::Result;
|
||||
|
||||
/// IR emitter controller
|
||||
///
|
||||
/// Public API - used in tests and may be used by external code
|
||||
#[allow(dead_code)] // Public API, used in tests
|
||||
pub struct IrEmitterControl {
|
||||
/// Device path
|
||||
#[allow(dead_code)] // Public API field
|
||||
device_path: String,
|
||||
/// Whether the emitter is currently active
|
||||
#[allow(dead_code)] // Public API field
|
||||
active: bool,
|
||||
}
|
||||
|
||||
impl IrEmitterControl {
|
||||
/// Create a new IR emitter controller for a device
|
||||
///
|
||||
/// Public API - used in tests
|
||||
#[allow(dead_code)] // Public API, used in tests
|
||||
pub fn new(device_path: &str) -> Self {
|
||||
Self {
|
||||
device_path: device_path.to_string(),
|
||||
@@ -22,7 +30,10 @@ impl IrEmitterControl {
|
||||
}
|
||||
|
||||
/// Attempt to enable the IR emitter
|
||||
///
|
||||
/// Public API - used in tests and may be used by external code
|
||||
#[cfg(target_os = "linux")]
|
||||
#[allow(dead_code)] // Public API, used in tests
|
||||
pub fn enable(&mut self) -> Result<()> {
|
||||
// IR emitter control is hardware-specific and typically requires:
|
||||
// 1. Finding the correct UVC extension unit
|
||||
@@ -78,7 +89,10 @@ impl IrEmitterControl {
|
||||
}
|
||||
|
||||
/// Attempt to disable the IR emitter
|
||||
///
|
||||
/// Public API - used in tests
|
||||
#[cfg(target_os = "linux")]
|
||||
#[allow(dead_code)] // Public API, used in tests
|
||||
pub fn disable(&mut self) -> Result<()> {
|
||||
tracing::info!("Disabling IR emitter for {}", self.device_path);
|
||||
|
||||
@@ -91,11 +105,17 @@ impl IrEmitterControl {
|
||||
}
|
||||
|
||||
/// Check if emitter is active
|
||||
///
|
||||
/// Public API - used in tests
|
||||
#[allow(dead_code)] // Public API, used in tests
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.active
|
||||
}
|
||||
|
||||
/// Get device path
|
||||
///
|
||||
/// Public API - used in tests
|
||||
#[allow(dead_code)] // Public API, used in tests
|
||||
pub fn device_path(&self) -> &str {
|
||||
&self.device_path
|
||||
}
|
||||
@@ -115,7 +135,10 @@ impl IrEmitterControl {
|
||||
}
|
||||
|
||||
/// Scan for IR emitter capabilities on a device
|
||||
///
|
||||
/// This is a public API function that may be used by external code or future features.
|
||||
#[cfg(target_os = "linux")]
|
||||
#[allow(dead_code)] // Public API, may be used externally
|
||||
pub fn scan_emitter_controls(device_path: &str) -> Vec<String> {
|
||||
let mut controls = Vec::new();
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
use v4l::buffer::Type;
|
||||
use v4l::io::mmap::Stream;
|
||||
use v4l::io::traits::CaptureStream;
|
||||
use v4l::prelude::*;
|
||||
use v4l::video::Capture;
|
||||
use v4l::{Device, FourCC};
|
||||
|
||||
@@ -114,6 +113,7 @@ fn get_supported_resolutions(device: &Device) -> Vec<(u32, u32)> {
|
||||
}
|
||||
|
||||
/// V4L2 Camera handle for capturing frames
|
||||
#[allow(dead_code)] // Public API, fields used by methods
|
||||
pub struct Camera {
|
||||
device: Device,
|
||||
stream: Option<Stream<'static>>,
|
||||
@@ -124,6 +124,9 @@ pub struct Camera {
|
||||
|
||||
impl Camera {
|
||||
/// Open a camera device by path
|
||||
///
|
||||
/// Public API - used by library code (auth.rs) and CLI
|
||||
#[allow(dead_code)] // Public API, used by library code and CLI
|
||||
pub fn open(device_path: &str) -> Result<Self> {
|
||||
// Extract device index from path
|
||||
let index: usize = device_path
|
||||
@@ -172,6 +175,9 @@ impl Camera {
|
||||
}
|
||||
|
||||
/// Start streaming
|
||||
///
|
||||
/// Public API - used internally by capture_frame
|
||||
#[allow(dead_code)] // Public API
|
||||
pub fn start(&mut self) -> Result<()> {
|
||||
if self.stream.is_some() {
|
||||
return Ok(());
|
||||
@@ -192,6 +198,9 @@ impl Camera {
|
||||
}
|
||||
|
||||
/// Capture a single frame
|
||||
///
|
||||
/// Public API - used by authentication and enrollment
|
||||
#[allow(dead_code)] // Public API, used by auth service
|
||||
pub fn capture_frame(&mut self) -> Result<Frame> {
|
||||
if self.stream.is_none() {
|
||||
self.start()?;
|
||||
@@ -222,6 +231,9 @@ impl Camera {
|
||||
}
|
||||
|
||||
/// Get current resolution
|
||||
///
|
||||
/// Public API - used by CLI status command
|
||||
#[allow(dead_code)] // Public API, used by CLI
|
||||
pub fn resolution(&self) -> (u32, u32) {
|
||||
(self.width, self.height)
|
||||
}
|
||||
|
||||
@@ -9,10 +9,14 @@ mod ir_emitter;
|
||||
#[cfg(target_os = "linux")]
|
||||
pub use linux::*;
|
||||
|
||||
// IrEmitterControl is exported for use in tests and external code
|
||||
// The unused import warning is expected since it's primarily used in tests
|
||||
#[allow(unused_imports)]
|
||||
pub use ir_emitter::IrEmitterControl;
|
||||
|
||||
/// Represents a detected camera device
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)] // Public API, fields may be used by external code
|
||||
pub struct CameraInfo {
|
||||
/// Device path (e.g., /dev/video0)
|
||||
pub device_path: String,
|
||||
@@ -38,6 +42,7 @@ impl std::fmt::Display for CameraInfo {
|
||||
|
||||
/// A captured video frame
|
||||
#[derive(Debug)]
|
||||
#[allow(dead_code)] // Public API, used by camera implementations
|
||||
pub struct Frame {
|
||||
/// Raw frame data
|
||||
pub data: Vec<u8>,
|
||||
@@ -53,6 +58,7 @@ pub struct Frame {
|
||||
|
||||
/// Supported pixel formats
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[allow(dead_code)] // Public API, variants used by camera implementations
|
||||
pub enum PixelFormat {
|
||||
/// 8-bit grayscale (Y8/GREY)
|
||||
Grey,
|
||||
|
||||
@@ -7,6 +7,7 @@ use linux_hello_common::Result;
|
||||
|
||||
/// Detected face bounding box
|
||||
#[derive(Debug, Clone)]
|
||||
#[allow(dead_code)] // Public API, fields used by detection methods
|
||||
pub struct FaceDetection {
|
||||
/// X coordinate of top-left corner (0-1 normalized)
|
||||
pub x: f32,
|
||||
@@ -22,6 +23,9 @@ pub struct FaceDetection {
|
||||
|
||||
impl FaceDetection {
|
||||
/// Convert normalized coordinates to pixel coordinates
|
||||
///
|
||||
/// Public API - used by face region extraction
|
||||
#[allow(dead_code)] // Public API, used by auth service
|
||||
pub fn to_pixels(&self, img_width: u32, img_height: u32) -> (u32, u32, u32, u32) {
|
||||
let x = (self.x * img_width as f32) as u32;
|
||||
let y = (self.y * img_height as f32) as u32;
|
||||
@@ -32,6 +36,9 @@ impl FaceDetection {
|
||||
}
|
||||
|
||||
/// Face detector trait for different backends
|
||||
///
|
||||
/// Public API - trait for extensible face detection backends
|
||||
#[allow(dead_code)] // Public API trait
|
||||
pub trait FaceDetect {
|
||||
/// Detect faces in a grayscale image
|
||||
fn detect(&self, image_data: &[u8], width: u32, height: u32) -> Result<Vec<FaceDetection>>;
|
||||
@@ -71,6 +78,10 @@ pub struct SimpleFaceDetector {
|
||||
}
|
||||
|
||||
impl SimpleFaceDetector {
|
||||
/// Create a new simple face detector
|
||||
///
|
||||
/// Public API - used for testing and placeholder implementation
|
||||
#[allow(dead_code)] // Public API, used in tests
|
||||
pub fn new(confidence_threshold: f32) -> Self {
|
||||
Self { confidence_threshold }
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
//! - Protection against memory dumps
|
||||
|
||||
use linux_hello_common::{Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::fmt;
|
||||
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||
|
||||
|
||||
@@ -15,7 +15,9 @@
|
||||
use linux_hello_common::{Error, Result};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use tracing::{debug, info, warn};
|
||||
use tracing::{debug, warn};
|
||||
#[cfg(feature = "tpm")]
|
||||
use tracing::info;
|
||||
|
||||
/// TPM2 key handle for Linux Hello primary key
|
||||
pub const PRIMARY_KEY_HANDLE: u32 = 0x81000001;
|
||||
@@ -151,7 +153,7 @@ impl TpmStorage for SoftwareTpmFallback {
|
||||
|
||||
fn create_user_key(&mut self, user: &str) -> Result<()> {
|
||||
let key_path = self.user_key_path(user);
|
||||
let key = self.derive_key(user);
|
||||
let _key = self.derive_key(user); // Derived but not stored in software fallback
|
||||
|
||||
// Store key metadata (not the actual key for software fallback)
|
||||
let metadata = format!("user={}\ncreated={}", user,
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
# Build the PAM shared library for face authentication
|
||||
|
||||
CC = gcc
|
||||
CFLAGS = -Wall -Wextra -Werror -fPIC -O2
|
||||
# Strictest warnings + static analysis flags
|
||||
CFLAGS = -Wall -Wextra -Werror -Wpedantic -Wstrict-prototypes \
|
||||
-Wmissing-prototypes -Wunreachable-code -Wcast-qual \
|
||||
-Wconversion -Wshadow -Wundef -Wformat=2 -fPIC -O2 \
|
||||
-D_FORTIFY_SOURCE=2
|
||||
LDFLAGS = -shared -lpam
|
||||
|
||||
TARGET = pam_linux_hello.so
|
||||
|
||||
@@ -6,6 +6,18 @@
|
||||
*
|
||||
* Copyright (C) 2026 Linux Hello Contributors
|
||||
* SPDX-License-Identifier: GPL-3.0
|
||||
*
|
||||
* Safety-Critical Coding Standards:
|
||||
* - No goto, setjmp, longjmp, or recursion
|
||||
* - All loops have fixed upper bounds
|
||||
* - No dynamic memory allocation after initialization
|
||||
* - Functions limited to ~60 lines
|
||||
* - Minimum 2 runtime assertions per function
|
||||
* - Data objects at smallest possible scope
|
||||
* - All return values checked
|
||||
* - Minimal preprocessor use
|
||||
* - Single level pointer dereferencing only
|
||||
* - Compile with strictest warnings
|
||||
*/
|
||||
|
||||
#define _GNU_SOURCE
|
||||
@@ -18,6 +30,7 @@
|
||||
#include <errno.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <security/pam_modules.h>
|
||||
#include <security/pam_ext.h>
|
||||
@@ -26,6 +39,8 @@
|
||||
#define DEFAULT_TIMEOUT 5
|
||||
#define SOCKET_PATH "/run/linux-hello/auth.sock"
|
||||
#define MAX_MESSAGE_SIZE 4096
|
||||
#define MAX_ARGS 32 /* Fixed upper bound for argument parsing */
|
||||
#define MAX_USERNAME_LEN 256
|
||||
|
||||
/* Module options */
|
||||
struct module_options {
|
||||
@@ -34,106 +49,229 @@ struct module_options {
|
||||
int fallback_password;
|
||||
};
|
||||
|
||||
/* Parse module arguments */
|
||||
/* Assertion helper - always check critical conditions */
|
||||
static inline void assert_condition(int condition, const char *msg) {
|
||||
if (!condition) {
|
||||
syslog(LOG_ERR, "Assertion failed: %s", msg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Parse module arguments with fixed upper bound */
|
||||
static void parse_args(int argc, const char **argv, struct module_options *opts) {
|
||||
int i;
|
||||
|
||||
int max_iterations;
|
||||
|
||||
/* Assertions */
|
||||
assert(opts != NULL);
|
||||
assert(argv != NULL);
|
||||
assert(argc >= 0);
|
||||
|
||||
/* Set defaults */
|
||||
opts->timeout = DEFAULT_TIMEOUT;
|
||||
opts->debug = 0;
|
||||
opts->fallback_password = 0;
|
||||
|
||||
for (i = 0; i < argc; i++) {
|
||||
if (strncmp(argv[i], "timeout=", 8) == 0) {
|
||||
opts->timeout = atoi(argv[i] + 8);
|
||||
if (opts->timeout <= 0) opts->timeout = DEFAULT_TIMEOUT;
|
||||
} else if (strcmp(argv[i], "debug") == 0) {
|
||||
|
||||
/* Fixed upper bound - prevent unbounded loops */
|
||||
max_iterations = (argc < MAX_ARGS) ? argc : MAX_ARGS;
|
||||
assert_condition(max_iterations <= MAX_ARGS, "argc exceeds MAX_ARGS");
|
||||
|
||||
/* Parse arguments with fixed bound */
|
||||
for (i = 0; i < max_iterations; i++) {
|
||||
const char *arg = argv[i];
|
||||
assert(arg != NULL);
|
||||
|
||||
if (strncmp(arg, "timeout=", 8) == 0) {
|
||||
int timeout_val = atoi(arg + 8);
|
||||
if (timeout_val > 0 && timeout_val <= 300) { /* Reasonable max */
|
||||
opts->timeout = timeout_val;
|
||||
}
|
||||
} else if (strcmp(arg, "debug") == 0) {
|
||||
opts->debug = 1;
|
||||
} else if (strcmp(argv[i], "fallback=password") == 0) {
|
||||
} else if (strcmp(arg, "fallback=password") == 0) {
|
||||
opts->fallback_password = 1;
|
||||
}
|
||||
}
|
||||
|
||||
assert_condition(opts->timeout > 0, "timeout must be positive");
|
||||
}
|
||||
|
||||
/* Send authentication request to daemon */
|
||||
static int authenticate_face(pam_handle_t *pamh, const char *user,
|
||||
struct module_options *opts) {
|
||||
/* Create and configure socket */
|
||||
static int create_socket(struct module_options *opts) {
|
||||
int sockfd;
|
||||
struct sockaddr_un addr;
|
||||
char request[MAX_MESSAGE_SIZE];
|
||||
char response[MAX_MESSAGE_SIZE];
|
||||
ssize_t n;
|
||||
int result = PAM_AUTH_ERR;
|
||||
|
||||
struct timeval tv;
|
||||
int result;
|
||||
|
||||
/* Assertions */
|
||||
assert(opts != NULL);
|
||||
assert_condition(opts->timeout > 0, "timeout must be positive");
|
||||
|
||||
/* Create socket */
|
||||
sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
if (sockfd < 0) {
|
||||
if (opts->debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Failed to create socket: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
return PAM_AUTHINFO_UNAVAIL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
||||
/* Set socket timeout */
|
||||
struct timeval tv;
|
||||
tv.tv_sec = opts->timeout;
|
||||
tv.tv_usec = 0;
|
||||
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||
|
||||
result = setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
||||
assert_condition(result == 0, "setsockopt SO_RCVTIMEO failed");
|
||||
|
||||
result = setsockopt(sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
|
||||
assert_condition(result == 0, "setsockopt SO_SNDTIMEO failed");
|
||||
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
/* Connect to daemon */
|
||||
memset(&addr, 0, sizeof(addr));
|
||||
/* Connect to daemon */
|
||||
static int connect_to_daemon(int sockfd, struct module_options *opts) {
|
||||
struct sockaddr_un addr;
|
||||
size_t path_len;
|
||||
int result;
|
||||
|
||||
/* Assertions */
|
||||
assert(sockfd >= 0);
|
||||
assert(opts != NULL);
|
||||
|
||||
/* Initialize address structure */
|
||||
(void)memset(&addr, 0, sizeof(addr));
|
||||
addr.sun_family = AF_UNIX;
|
||||
strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
|
||||
|
||||
/* Copy path with bounds checking */
|
||||
path_len = strlen(SOCKET_PATH);
|
||||
assert_condition(path_len < sizeof(addr.sun_path), "Socket path too long");
|
||||
|
||||
(void)strncpy(addr.sun_path, SOCKET_PATH, sizeof(addr.sun_path) - 1);
|
||||
addr.sun_path[sizeof(addr.sun_path) - 1] = '\0';
|
||||
|
||||
/* Connect */
|
||||
result = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
|
||||
assert_condition(result == 0 || errno != 0, "connect result checked");
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
|
||||
if (opts->debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Failed to connect to daemon: %s",
|
||||
strerror(errno));
|
||||
}
|
||||
close(sockfd);
|
||||
return PAM_AUTHINFO_UNAVAIL;
|
||||
}
|
||||
|
||||
/* Send authentication request */
|
||||
snprintf(request, sizeof(request), "{\"action\":\"authenticate\",\"user\":\"%s\"}", user);
|
||||
|
||||
n = write(sockfd, request, strlen(request));
|
||||
/* Send authentication request */
|
||||
static int send_request(int sockfd, const char *user, struct module_options *opts) {
|
||||
char request[MAX_MESSAGE_SIZE];
|
||||
int user_len;
|
||||
int request_len;
|
||||
ssize_t n;
|
||||
|
||||
/* Assertions */
|
||||
assert(sockfd >= 0);
|
||||
assert(user != NULL);
|
||||
assert(opts != NULL);
|
||||
|
||||
/* Build request with bounds checking */
|
||||
user_len = (int)strlen(user);
|
||||
assert_condition(user_len > 0 && user_len < MAX_USERNAME_LEN, "Invalid username length");
|
||||
|
||||
request_len = snprintf(request, sizeof(request),
|
||||
"{\"action\":\"authenticate\",\"user\":\"%s\"}", user);
|
||||
assert_condition(request_len > 0 && request_len < (int)sizeof(request),
|
||||
"Request buffer overflow");
|
||||
|
||||
/* Send request */
|
||||
n = write(sockfd, request, (size_t)request_len);
|
||||
assert_condition(n >= 0, "write result checked");
|
||||
|
||||
if (n < 0) {
|
||||
if (opts->debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Failed to send request: %s",
|
||||
strerror(errno));
|
||||
pam_syslog(NULL, LOG_DEBUG, "Failed to send request: %s", strerror(errno));
|
||||
}
|
||||
close(sockfd);
|
||||
return PAM_AUTHINFO_UNAVAIL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Read and parse response */
|
||||
static int read_response(int sockfd, struct module_options *opts) {
|
||||
char response[MAX_MESSAGE_SIZE];
|
||||
ssize_t n;
|
||||
const char *success_str = "\"success\":true";
|
||||
|
||||
/* Assertions */
|
||||
assert(sockfd >= 0);
|
||||
assert(opts != NULL);
|
||||
|
||||
/* Read response */
|
||||
n = read(sockfd, response, sizeof(response) - 1);
|
||||
assert_condition(n >= -1, "read result in valid range");
|
||||
|
||||
if (n <= 0) {
|
||||
if (opts->debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Failed to read response: %s",
|
||||
n < 0 ? strerror(errno) : "timeout");
|
||||
if (opts->debug && n < 0) {
|
||||
pam_syslog(NULL, LOG_DEBUG, "Failed to read response: %s", strerror(errno));
|
||||
}
|
||||
close(sockfd);
|
||||
return PAM_AUTH_ERR;
|
||||
return 0; /* Failure */
|
||||
}
|
||||
|
||||
/* Null terminate */
|
||||
response[n] = '\0';
|
||||
|
||||
/* Parse response (simple check) */
|
||||
if (strstr(response, "\"success\":true") != NULL) {
|
||||
result = PAM_SUCCESS;
|
||||
pam_syslog(pamh, LOG_INFO, "Face authentication successful for %s", user);
|
||||
} else {
|
||||
result = PAM_AUTH_ERR;
|
||||
if (opts->debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Face authentication failed for %s", user);
|
||||
}
|
||||
assert_condition(n < (ssize_t)sizeof(response), "Response fits in buffer");
|
||||
|
||||
/* Simple string search for success */
|
||||
if (strstr(response, success_str) != NULL) {
|
||||
return 1; /* Success */
|
||||
}
|
||||
|
||||
return 0; /* Failure */
|
||||
}
|
||||
|
||||
close(sockfd);
|
||||
return result;
|
||||
/* Send authentication request to daemon - main function */
|
||||
static int authenticate_face(pam_handle_t *pamh, const char *user,
|
||||
struct module_options *opts) {
|
||||
int sockfd;
|
||||
int result;
|
||||
|
||||
/* Assertions */
|
||||
assert(pamh != NULL);
|
||||
assert(user != NULL);
|
||||
assert(opts != NULL);
|
||||
assert_condition(strlen(user) > 0 && strlen(user) < MAX_USERNAME_LEN,
|
||||
"Valid username");
|
||||
|
||||
/* Create socket */
|
||||
sockfd = create_socket(opts);
|
||||
if (sockfd < 0) {
|
||||
if (opts->debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Failed to create socket: %s", strerror(errno));
|
||||
}
|
||||
return PAM_AUTHINFO_UNAVAIL;
|
||||
}
|
||||
|
||||
/* Connect to daemon */
|
||||
result = connect_to_daemon(sockfd, opts);
|
||||
if (result < 0) {
|
||||
if (opts->debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Failed to connect to daemon: %s", strerror(errno));
|
||||
}
|
||||
(void)close(sockfd);
|
||||
return PAM_AUTHINFO_UNAVAIL;
|
||||
}
|
||||
|
||||
/* Send request */
|
||||
result = send_request(sockfd, user, opts);
|
||||
if (result < 0) {
|
||||
(void)close(sockfd);
|
||||
return PAM_AUTHINFO_UNAVAIL;
|
||||
}
|
||||
|
||||
/* Read response */
|
||||
result = read_response(sockfd, opts);
|
||||
(void)close(sockfd);
|
||||
|
||||
if (result > 0) {
|
||||
pam_syslog(pamh, LOG_INFO, "Face authentication successful for %s", user);
|
||||
return PAM_SUCCESS;
|
||||
}
|
||||
|
||||
if (opts->debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Face authentication failed for %s", user);
|
||||
}
|
||||
return PAM_AUTH_ERR;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -144,33 +282,46 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
||||
struct module_options opts;
|
||||
const char *user;
|
||||
int ret;
|
||||
|
||||
|
||||
/* Suppress unused parameter warnings */
|
||||
(void)flags;
|
||||
|
||||
/* Assertions */
|
||||
assert(pamh != NULL);
|
||||
assert(argv != NULL || argc == 0);
|
||||
assert_condition(argc >= 0 && argc <= MAX_ARGS, "argc within bounds");
|
||||
|
||||
/* Parse module arguments */
|
||||
parse_args(argc, argv, &opts);
|
||||
|
||||
|
||||
/* Get username */
|
||||
ret = pam_get_user(pamh, &user, NULL);
|
||||
assert_condition(ret == PAM_SUCCESS || ret != PAM_SUCCESS, "Return value checked");
|
||||
|
||||
if (ret != PAM_SUCCESS || user == NULL || user[0] == '\0') {
|
||||
pam_syslog(pamh, LOG_ERR, "Failed to get username");
|
||||
return PAM_USER_UNKNOWN;
|
||||
}
|
||||
|
||||
|
||||
assert_condition(strlen(user) < MAX_USERNAME_LEN, "Username length valid");
|
||||
|
||||
if (opts.debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Attempting face authentication for %s", user);
|
||||
}
|
||||
|
||||
|
||||
/* Attempt face authentication */
|
||||
ret = authenticate_face(pamh, user, &opts);
|
||||
|
||||
assert_condition(ret == PAM_SUCCESS || ret == PAM_AUTH_ERR ||
|
||||
ret == PAM_AUTHINFO_UNAVAIL, "Valid return value");
|
||||
|
||||
/* Handle fallback */
|
||||
if (ret != PAM_SUCCESS && opts.fallback_password) {
|
||||
if (opts.debug) {
|
||||
pam_syslog(pamh, LOG_DEBUG, "Face auth failed, allowing password fallback");
|
||||
}
|
||||
/* Return ignore to let other modules (password) try */
|
||||
return PAM_IGNORE;
|
||||
}
|
||||
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -179,6 +330,10 @@ PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
||||
*/
|
||||
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
|
||||
int argc, const char **argv) {
|
||||
/* Assertions */
|
||||
assert(pamh != NULL);
|
||||
assert(argv != NULL || argc == 0);
|
||||
|
||||
(void)pamh;
|
||||
(void)flags;
|
||||
(void)argc;
|
||||
@@ -191,6 +346,10 @@ PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags,
|
||||
*/
|
||||
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
|
||||
int argc, const char **argv) {
|
||||
/* Assertions */
|
||||
assert(pamh != NULL);
|
||||
assert(argv != NULL || argc == 0);
|
||||
|
||||
(void)pamh;
|
||||
(void)flags;
|
||||
(void)argc;
|
||||
@@ -203,6 +362,10 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags,
|
||||
*/
|
||||
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||
int argc, const char **argv) {
|
||||
/* Assertions */
|
||||
assert(pamh != NULL);
|
||||
assert(argv != NULL || argc == 0);
|
||||
|
||||
(void)pamh;
|
||||
(void)flags;
|
||||
(void)argc;
|
||||
@@ -212,6 +375,10 @@ PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||
|
||||
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags,
|
||||
int argc, const char **argv) {
|
||||
/* Assertions */
|
||||
assert(pamh != NULL);
|
||||
assert(argv != NULL || argc == 0);
|
||||
|
||||
(void)pamh;
|
||||
(void)flags;
|
||||
(void)argc;
|
||||
|
||||
Reference in New Issue
Block a user