/* * pam_authforge_pending.so — backstop for AuthForge first-login enrollment. * * Behavior: * 1. Read PAM_USER. * 2. Reject usernames that would let the path computation escape * /var/lib/authforge/pending/. * 3. stat() /var/lib/authforge/pending/. * - present -> emit user-facing message + return PAM_AUTH_ERR. * - ENOENT -> return PAM_IGNORE (let the rest of the stack decide). * - other -> log + return PAM_AUTH_ERR (fail closed). * * Recovery codes (Phase 12) wire into a separate hook that's a no-op here. */ #define PAM_SM_AUTH #include #include #include #include #include #include #include #define PENDING_DIR "/var/lib/authforge/pending/" #define USER_MSG "Account setup incomplete. Please complete enrollment in the Authentication app." static int username_is_safe(const char *u) { if (u == NULL || u[0] == '\0') return 0; if (strcmp(u, ".") == 0 || strcmp(u, "..") == 0) return 0; for (const char *p = u; *p; ++p) { if (*p == '/') return 0; } /* Reject any '..' substring as a defense-in-depth measure. */ if (strstr(u, "..") != NULL) return 0; return 1; } static int pending_flag_present(const char *user) { char path[1024]; int n = snprintf(path, sizeof(path), "%s%s", PENDING_DIR, user); if (n < 0 || (size_t)n >= sizeof(path)) return -1; struct stat st; if (stat(path, &st) == 0) return 1; if (errno == ENOENT) return 0; return -1; } PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { (void)flags; (void)argc; (void)argv; const char *user = NULL; if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS || user == NULL) { pam_syslog(pamh, LOG_ERR, "authforge_pending: pam_get_user failed"); return PAM_AUTH_ERR; } if (!username_is_safe(user)) { pam_syslog(pamh, LOG_ERR, "authforge_pending: rejected unsafe username"); return PAM_AUTH_ERR; } int present = pending_flag_present(user); if (present < 0) { pam_syslog(pamh, LOG_ERR, "authforge_pending: stat error for %s (errno=%d)", user, errno); return PAM_AUTH_ERR; /* fail closed */ } if (present == 0) { return PAM_IGNORE; } pam_error(pamh, "%s", USER_MSG); pam_syslog(pamh, LOG_INFO, "authforge_pending: denied %s (pending flag present)", user); return PAM_AUTH_ERR; } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { (void)pamh; (void)flags; (void)argc; (void)argv; return PAM_SUCCESS; }