pamtester

package module
v0.0.0-...-2abeb25 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: MIT Imports: 6 Imported by: 0

README

pamtester

Verify and change users' login passwords via Linux PAM using purego — no cgo required.

Example

package main

import (
    "errors"
    "fmt"
    "os"

    "github.com/phuslu/pamtester"
)

func main() {
    t, err := pamtester.Start("root", nil)
    if err != nil {
        fmt.Fprintln(os.Stderr, "pam start failed:", err)
        os.Exit(1)
    }
    defer t.Close()

    // Verify password (pam_authenticate)
    err = t.Authenticate("secret")
    switch {
    case err == nil:
        fmt.Println("password correct")
    case errors.Is(err, pamtester.ErrAuth):
        fmt.Println("wrong password")
    default:
        fmt.Fprintln(os.Stderr, "auth failed:", err)
        os.Exit(1)
    }

    // Change password (equivalent to passwd; writing /etc/shadow
    // generally requires root on Debian/Ubuntu)
    _ = t.ChangeAuthTok("secret", "n3w-Secret!")
}

CLI

go run ./cmd/pamtester/

Documentation

Overview

Package pamtester verifies and changes users' login passwords by calling the system's libpam directly via purego (no cgo required).

The core type is Transaction, which corresponds to one PAM transaction (pam_start .. pam_end). Authenticate, AcctMgmt and ChangeAuthTok are its methods. The top-level functions CheckUserPassword and ChangeUserPassword are convenience wrappers around a single transaction.

How it works:

Linux commands like login, su, sudo, and passwd all authenticate passwords
through PAM (Pluggable Authentication Modules). The flow is:
  1. pam_start()          Opens a PAM transaction and registers a
                           conversation callback function.
  2. pam_authenticate()   PAM internally uses modules like pam_unix.so,
                           which call back into the registered conversation
                           function to request the password. We supply the
                           password string to be verified in that callback.
  3. pam_acct_mgmt()      Checks account validity (expired, locked, ...).
  4. pam_end()            Closes the transaction.

The actual password comparison (reading /etc/shadow and hashing) is done by
pam_unix.so. Non-root processes can typically verify their own password:
when pam_unix lacks permission to read /etc/shadow directly, it delegates
to the setuid-root unix_chkpwd helper, so this library does not require
root privileges.

Dependencies:

go get github.com/ebitengine/purego

Index

Constants

This section is empty.

Variables

View Source
var PamService = "passwd"

PamService is the default PAM service configuration file under /etc/pam.d/, used when Options.Service is empty.

Functions

This section is empty.

Types

type Error

type Error int32

Error is a PAM return code (from <security/_pam_types.h>) as a Go error. Errors returned by this package wrap an Error, so callers can classify failures with errors.Is:

if errors.Is(err, pamtester.ErrAuth) { ... wrong password ... }
const (
	ErrOpen                Error = 1  // PAM_OPEN_ERR
	ErrSymbol              Error = 2  // PAM_SYMBOL_ERR
	ErrService             Error = 3  // PAM_SERVICE_ERR
	ErrSystem              Error = 4  // PAM_SYSTEM_ERR
	ErrBuf                 Error = 5  // PAM_BUF_ERR
	ErrPermDenied          Error = 6  // PAM_PERM_DENIED
	ErrAuth                Error = 7  // PAM_AUTH_ERR — wrong password
	ErrCredInsufficient    Error = 8  // PAM_CRED_INSUFFICIENT
	ErrAuthinfoUnavail     Error = 9  // PAM_AUTHINFO_UNAVAIL
	ErrUserUnknown         Error = 10 // PAM_USER_UNKNOWN
	ErrMaxTries            Error = 11 // PAM_MAXTRIES
	ErrNewAuthTokReqd      Error = 12 // PAM_NEW_AUTHTOK_REQD — password correct but expired, must be changed
	ErrAcctExpired         Error = 13 // PAM_ACCT_EXPIRED
	ErrSession             Error = 14 // PAM_SESSION_ERR
	ErrCredUnavail         Error = 15 // PAM_CRED_UNAVAIL
	ErrCredExpired         Error = 16 // PAM_CRED_EXPIRED
	ErrCred                Error = 17 // PAM_CRED_ERR
	ErrNoModuleData        Error = 18 // PAM_NO_MODULE_DATA
	ErrConv                Error = 19 // PAM_CONV_ERR
	ErrAuthTok             Error = 20 // PAM_AUTHTOK_ERR
	ErrAuthTokRecovery     Error = 21 // PAM_AUTHTOK_RECOVERY_ERR — e.g. wrong old password in ChangeAuthTok
	ErrAuthTokLockBusy     Error = 22 // PAM_AUTHTOK_LOCK_BUSY
	ErrAuthTokDisableAging Error = 23 // PAM_AUTHTOK_DISABLE_AGING
	ErrTryAgain            Error = 24 // PAM_TRY_AGAIN
	ErrIgnore              Error = 25 // PAM_IGNORE
	ErrAbort               Error = 26 // PAM_ABORT
	ErrAuthTokExpired      Error = 27 // PAM_AUTHTOK_EXPIRED
	ErrModuleUnknown       Error = 28 // PAM_MODULE_UNKNOWN
	ErrBadItem             Error = 29 // PAM_BAD_ITEM
	ErrConvAgain           Error = 30 // PAM_CONV_AGAIN
	ErrIncomplete          Error = 31 // PAM_INCOMPLETE
)

PAM return codes (Linux-PAM numbering).

func (Error) Error

func (e Error) Error() string

type Options

type Options struct {
	// Service selects the PAM service file under /etc/pam.d/.
	// Empty means the package-level default PamService.
	Service string

	// RHost, if non-empty, is set as PAM_RHOST — the remote host the request
	// originates from. Services performing authentication on behalf of remote
	// clients should set this so that modules like pam_faillock/pam_access
	// and audit logs record the real source.
	RHost string

	// TTY, if non-empty, is set as PAM_TTY.
	TTY string

	// RUser, if non-empty, is set as PAM_RUSER — the requesting user.
	RUser string
}

Options carries optional parameters for Start. The zero value is valid.

type Transaction

type Transaction struct {
	// contains filtered or unexported fields
}

Transaction represents one PAM transaction, i.e. a pam_start .. pam_end lifetime. Obtain one with Start, and always Close it. A Transaction is safe for sequential use; its methods serialize via an internal mutex.

func Start

func Start(user string, opts *Options) (*Transaction, error)

Start opens a PAM transaction for user. opts may be nil. The caller must Close the returned Transaction.

func (*Transaction) AcctMgmt

func (t *Transaction) AcctMgmt() error

AcctMgmt runs pam_acct_mgmt: it checks that the account is valid — not expired or locked, and access not denied by modules like pam_access or pam_time. A special case is an error matching ErrNewAuthTokReqd: the account is fine but the password has expired and must be changed (e.g. via ChangeAuthTok).

func (*Transaction) Authenticate

func (t *Transaction) Authenticate(password string) error

Authenticate verifies password via pam_authenticate: regardless of what the PAM module asks (typically "Password:"), it responds uniformly with the given password. Returns nil if the password is correct; a wrong password yields an error matching ErrAuth (use errors.Is).

Note that Authenticate only proves the password is right — call AcctMgmt afterwards to check that the account itself is still usable.

func (*Transaction) ChangeAuthTok

func (t *Transaction) ChangeAuthTok(oldPassword, newPassword string) error

ChangeAuthTok changes the user's password via pam_chauthtok. When run without root privileges PAM first asks for the current password ("(current) UNIX password:"), then for the new one twice; as root the old password is usually not requested and oldPassword may be empty.

Prompts containing "new" (case-insensitive) are answered with newPassword, anything else with oldPassword. Go programs never call setlocale(), so libpam prompts stay untranslated C-locale English and this matching is reliable even when LANG is set.

Failures (wrong oldPassword, newPassword rejected by quality checks, or no permission to write /etc/shadow) yield errors matching ErrAuthTok or ErrAuthTokRecovery depending on the module. Note that unlike password verification, actually updating the password requires write access to /etc/shadow: root always works, while non-root only works on systems whose pam_unix ships a setuid unix_update helper (e.g. RHEL; Debian/Ubuntu do not — there the passwd(1) command relies on its own setuid bit).

func (*Transaction) Close

func (t *Transaction) Close() error

Close ends the transaction with pam_end and releases all resources. Calling Close more than once is harmless.

Directories

Path Synopsis
cmd
pamtester command
Command pamtester is a small CLI demo that reads a password from stdin and verifies it against the current user's PAM account.
Command pamtester is a small CLI demo that reads a password from stdin and verifies it against the current user's PAM account.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL