Replaces the Phase 0 stub. Reads PAM_USER, rejects usernames containing /, \0, .., or that are . / .. / empty. stat()s /var/lib/authforge/pending/<user>: present -> emits the design-doc user-facing message + PAM_AUTH_ERR; ENOENT -> PAM_IGNORE (lets the rest of the stack decide); other errno -> logs and fails closed (PAM_AUTH_ERR). Builds clean against libpam0g-dev with -Wall -Wextra -Werror -fPIC -O2. Resulting .so is a 16KB stripped-with-buildid x86-64 ELF; passes file(1) sanity. pamtester smoke recipe lands in Task B2 alongside the test PAM stack file.
83 lines
2.7 KiB
C
83 lines
2.7 KiB
C
/*
|
|
* 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/<user>.
|
|
* - 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 <security/pam_modules.h>
|
|
#include <security/pam_ext.h>
|
|
|
|
#include <errno.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <sys/stat.h>
|
|
|
|
#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;
|
|
}
|