package auth import ( "fmt" "os" ) // PAMService is the name of the PAM service nadir authenticates against. // It maps to /etc/pam.d/. We use a dedicated service rather than // a stock one like "login" so we control exactly which modules run during // authentication. See README.md ("PAM service") for the full rationale. const PAMService = "nadir" var pamServicePath = "/etc/pam.d/" + PAMService // pamServiceContent is the minimal stack nadir needs: verify the password // against /etc/shadow and confirm the account is valid. It deliberately omits // pam_fprintd (blocks ~30s waiting for a fingerprint swipe that never comes), // pam_systemd, pam_env, and the rest of the distro's login stack. const pamServiceContent = `#%PAM-1.0 # Managed by nadir. Do not rely on hand edits surviving - nadir recreates this # file on startup only if it is missing. Minimal auth stack: verify the # password against /etc/shadow and confirm the account is valid. Deliberately # omits pam_fprintd (blocks ~30s on a fingerprint swipe), pam_systemd, pam_env. auth required pam_unix.so account required pam_unix.so ` // EnsurePAMService writes the PAM service file if it is missing. nadir already // runs as root (pam_unix needs to read /etc/shadow), so it can install its own // PAM config rather than relying on a separate install step. // // It will not overwrite an existing file: an admin who has customized // /etc/pam.d/nadir keeps their version. Returns an error only on a real I/O // problem so main can fail loudly instead of later looking like bad // credentials (a missing file falls through to pam_deny via /etc/pam.d/other). func EnsurePAMService() error { switch _, err := os.Stat(pamServicePath); { case err == nil: return nil // already present - leave admin customizations intact case os.IsNotExist(err): // fall through and create it default: return fmt.Errorf("stat %s: %w", pamServicePath, err) } if err := os.WriteFile(pamServicePath, []byte(pamServiceContent), 0644); err != nil { return fmt.Errorf("write %s: %w (need root, and /etc/pam.d must be writable)", pamServicePath, err) } return nil }