64 lines
2.0 KiB
Go
64 lines
2.0 KiB
Go
//go:build linux && cgo
|
|
|
|
package main
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/msteinert/pam/v2"
|
|
)
|
|
|
|
// pamAuthAvailable reports that PAM authentication is compiled into this build.
|
|
const pamAuthAvailable = true
|
|
|
|
// pamServiceName returns the PAM service to use for the auth-only check. This
|
|
// is the name of a file under /etc/pam.d. Override with SSHPANEL_PAM_SERVICE;
|
|
// it defaults to "login", which exists on every mainstream distro and runs the
|
|
// standard unix auth stack (common-auth / system-auth).
|
|
func pamServiceName() string {
|
|
if s := strings.TrimSpace(os.Getenv("SSHPANEL_PAM_SERVICE")); s != "" {
|
|
return s
|
|
}
|
|
return "login"
|
|
}
|
|
|
|
// authenticatePAM verifies password against the Linux PAM stack for username.
|
|
//
|
|
// It runs ONLY the auth phase (pam_authenticate) — no account management
|
|
// (pam_acct_mgmt), no session, no credential setup, and nothing to do with the
|
|
// system SSH daemon. This is the "just the auth" behaviour: the supplied
|
|
// password is checked against the system account exactly as PAM's auth modules
|
|
// would, and nothing else. Returns nil on success, an error on failure.
|
|
//
|
|
// The panel runs as root, so pam_unix can read /etc/shadow to verify the hash
|
|
// (including yescrypt/sha512crypt) for any local account.
|
|
func authenticatePAM(username, password string) error {
|
|
if username == "" {
|
|
return errors.New("pam: empty username")
|
|
}
|
|
t, err := pam.StartFunc(pamServiceName(), username, func(s pam.Style, msg string) (string, error) {
|
|
switch s {
|
|
case pam.PromptEchoOff, pam.PromptEchoOn:
|
|
return password, nil
|
|
case pam.ErrorMsg, pam.TextInfo:
|
|
// Informational messages from modules; nothing to return.
|
|
return "", nil
|
|
default:
|
|
return "", fmt.Errorf("pam: unsupported conversation style %v", s)
|
|
}
|
|
})
|
|
if err != nil {
|
|
return fmt.Errorf("pam start: %w", err)
|
|
}
|
|
defer func() { _ = t.End() }()
|
|
|
|
// Silent keeps modules from writing to stdout/syslog noise; auth phase only.
|
|
if err := t.Authenticate(pam.Silent); err != nil {
|
|
return fmt.Errorf("pam auth: %w", err)
|
|
}
|
|
return nil
|
|
}
|