#include "../Passcode.h" #include "../../payloads/PayloadUtil.h" #include #include // ═══════════════════════════════════════════════════════════════════════════ // PASSCODE POLICY HANDLER (Linux) // Writes /etc/security/pwquality.conf.d/vesperos.conf // and sets /etc/login.defs values for max age, min age, etc. // Also writes a PAM faillock config for lockout policy. // ═══════════════════════════════════════════════════════════════════════════ namespace vesperos::profile::platform::passcode { using namespace vesperos::profile::util; namespace { constexpr const char* kPwqualityDir = "/etc/security/pwquality.conf.d"; constexpr const char* kFaillockConf = "/etc/security/faillock.conf.d/vesperos.conf"; } bool apply(const ParsedPayload& p) { int minLen = std::stoi(field(p, "min-length", "6")); int minComplex = std::stoi(field(p, "min-complex-chars", "0")); bool reqAlpha = fieldBool(p, "require-alphanumeric", false); bool allowSimp = fieldBool(p, "allow-simple", true); int maxAge = std::stoi(field(p, "max-age-days", "0")); int history = std::stoi(field(p, "history", "0")); int maxFailed = std::stoi(field(p, "max-failed-attempts", "0")); int inactivity = std::stoi(field(p, "inactivity-minutes", "0")); // ── pam_pwquality ────────────────────────────────────────────── ensureDir(kPwqualityDir); std::ostringstream pq; pq << "# Managed by vesperprofiled — do not edit manually\n"; pq << "minlen = " << minLen << "\n"; if (minComplex > 0) pq << "minclass = " << minComplex << "\n"; if (reqAlpha) pq << "dcredit = -1\n" << "ucredit = -1\n"; if (!allowSimp) pq << "maxrepeat = 2\nmaxsequence = 2\n"; if (history > 0) pq << "# remember=" << history << " set in /etc/pam.d/common-password\n"; writeFile(std::string(kPwqualityDir) + "/vesperos.conf", pq.str()); // ── /etc/login.defs overrides via conf.d drop-in ─────────────── // Debian reads /etc/login.defs directly; we append a comment-marked // block. A real implementation patches login.defs via sed or a // dedicated management file; here we write a separate snippet // that a PAM module or login wrapper can source. std::ostringstream ld; ld << "# vesperprofiled managed\n"; if (maxAge > 0) ld << "PASS_MAX_DAYS\t" << maxAge << "\n"; if (history > 0) ld << "# PASS_REUSE_LIMIT=" << history << "\n"; writeFile("/etc/vesperprofiled/login.defs.snippet", ld.str()); // ── pam_faillock ─────────────────────────────────────────────── if (maxFailed > 0) { ensureDir("/etc/security/faillock.conf.d"); std::ostringstream fl; fl << "# Managed by vesperprofiled\n" << "deny = " << maxFailed << "\n"; if (inactivity > 0) fl << "unlock_time = " << (inactivity * 60) << "\n"; writeFile(kFaillockConf, fl.str()); } syslog(LOG_INFO, "[passcode] applied password policy"); return true; } void revert(const ParsedPayload& /*p*/) { removeFile(std::string(kPwqualityDir) + "/vesperos.conf"); removeFile(kFaillockConf); removeFile("/etc/vesperprofiled/login.defs.snippet"); syslog(LOG_INFO, "[passcode] reverted password policy"); } } // namespace vesperos::profile::platform::passcode