pam
pam
Production-grade, type-safe Linux Pluggable Authentication Modules (PAM) binding for Crystal.
Provides C FFI bindings (libpam.so), memory safety contracts (allocating conversation responses via C malloc/free to prevent glibc invalid pointer crashes), type-safe enums and exceptions, customizable conversation handlers, and block-scoped RAII context handles.
Installation
Add the dependency to your shard.yml:
dependencies:
pam:
gitlab: renich/pam
Run shards install.
Quick Usage
1. Simple User Authentication
require "pam"
# Authenticate user credentials against PAM service ("login", "system-auth", "sudo")
if Pam.authenticate("system-auth", "alice", "secret123")
puts "Access granted"
else
puts "Access denied"
end
# Raising variant (raises Pam::AuthError on failure)
begin
Pam.authenticate!("system-auth", "alice", "secret123")
puts "Authentication successful"
rescue ex : Pam::AuthError
puts "Authentication failed: #{ex.message}"
end
2. Block-Scoped Context & Session Management
Pam::Context.open guarantees pam_end is invoked upon block exit, preserving PAM transaction status.
require "pam"
Pam::Context.open("system-auth", "alice") do |ctx|
# Authenticate credentials
ctx.authenticate!
# Verify account policy compliance (expiration, login hours)
ctx.acct_mgmt!
# Establish credentials and open PAM session
ctx.setcred(Pam::Flags::EstablishCred)
ctx.open_session
# Access PAM transaction metadata items
ctx.tty = "tty1"
ctx.rhost = "192.168.1.50"
puts "User: #{ctx.user}, TTY: #{ctx.tty}, Remote Host: #{ctx.rhost}"
# Manage PAM environment variables
ctx.setenv("XDG_RUNTIME_DIR", "/run/user/1000")
env = ctx.env # Returns Hash(String, String)
puts "PAM Environment: #{env}"
# Perform authenticated system work...
# Clean up session
ctx.close_session
end
3. Password Token Change (pam_chauthtok)
require "pam"
Pam::Context.open("passwd", "alice") do |ctx|
ctx.authenticate!
ctx.chauthtok(Pam::Flags::None)
puts "Password updated successfully"
end
4. Custom Conversation Handlers
Implement custom prompt handlers by inheriting from Pam::Conversation:
require "pam"
class ConsoleConversation < Pam::Conversation
def call(messages : Array(Pam::Message)) : Array(Pam::Response)
messages.map do |msg|
case msg.style
when Pam::MessageStyle::PromptEchoOff
# Prompt for hidden input (password/PIN)
print msg.text
Response.new(STDIN.noecho(&.gets).try(&.chomp))
when Pam::MessageStyle::PromptEchoOn
# Prompt for visible text
print msg.text
Response.new(STDIN.gets.try(&.chomp))
when Pam::MessageStyle::ErrorMsg, Pam::MessageStyle::TextInfo
puts msg.text
Response.new(nil, 0)
else
Response.new(nil, 0)
end
end
end
end
Pam::Context.open("system-auth", "alice", ConsoleConversation.new) do |ctx|
ctx.authenticate!
end
Build Targets (GNUmakefile)
make all # Run specs and build API documentation in docs/technical/api
make docs # Generate HTML API documentation via `crystal docs -o docs/technical/api`
make test # Run Crystal test suite (`crystal spec`, use VERBOSE=1 for verbose output)
make check # Run Ameba static code analysis
make clean # Remove generated documentation & build caches
License
This project is licensed under the GNU General Public License v3.0 or later (GPL-3.0-or-later). See LICENSE for details.
pam
- 0
- 0
- 0
- 0
- 2
- about 3 hours ago
- August 13, 2026
GNU General Public License v3.0 only
Thu, 13 Aug 2026 14:19:21 GMT