在pam_sm_open_session中调用pam_prompt返回会话失败错误的问题
问题分析与解决方案
核心原因
PAM的**会话阶段(pam_sm_open_session)和认证阶段(pam_sm_authenticate)**上下文环境完全不同:
- 认证阶段是用户交互的核心阶段,PAM服务(如ssh、login)会在此绑定有效的对话函数(
PAM_CONV),确保能与用户终端进行输入输出交互。 - 会话阶段的核心作用是初始化会话资源,多数PAM服务在认证完成后会断开或切换终端交互通道,此时
pam_prompt找不到可用的输入输出渠道,因此返回PAM_SESSION_ERR;而PAM_TEXT_INFO虽调用成功,但因无输出渠道导致内容无法显示。
强行在会话阶段调用pam_authenticate或pam_open_session会破坏PAM流程上下文,触发错误是必然结果。
可行解决方案
1. 将交互逻辑移至认证阶段
这是最可靠的方案,认证阶段天然支持用户交互。修改思路:
- 在
pam_sm_authenticate中检查用户会话数与权限,触发pam_prompt让用户选择要关闭的会话。 - 用户完成选择并清理会话后,再进入会话阶段;若用户选择退出,直接在认证阶段返回错误终止流程。
示例修改后的认证阶段代码片段:
/* PAM entry point for authentication */ DLL_EXPORT int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *user; int res = pam_get_user(pamh, &user, NULL); if (res != PAM_SUCCESS || user == NULL) { return PAM_USER_UNKNOWN; } // 获取用户会话数与权限 int num_sessions = get_user_sessions(user); bool user_has_permissions = check_user_perm(user); if (num_sessions > MAX_ALLOWED_SESSIONS_PER_USER) { if (user_has_permissions) { char *session_id; // 此处调用pam_prompt可正常交互 res = pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &session_id, "请输入要关闭的会话ID(输入exit取消登录):", NULL); if (res != PAM_SUCCESS) { return PAM_CONV_ERR; } if (!strcmp(session_id, "exit")) { pam_free(pamh, session_id); return PAM_AUTH_ERR; } else { // 执行关闭会话操作 if (kill_session(session_id) != 0) { pam_free(pamh, session_id); return PAM_AUTH_ERR; } pam_free(pamh, session_id); } } else { return PAM_AUTH_ERR; } } return PAM_SUCCESS; }
2. 会话阶段无交互替代方案
如果必须在会话阶段处理,放弃用户交互,改用自动策略:
- 自动关闭用户最久未使用的会话
- 记录日志并拒绝新会话,引导用户通过
pkill、loginctl等工具手动清理
示例自动清理逻辑:
/* PAM entry point for session creation */ DLL_EXPORT int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) { const char *user; int res = pam_get_user(pamh, &user, NULL); if (res != PAM_SUCCESS || user == NULL) { return PAM_USER_UNKNOWN; } int num_sessions = get_user_sessions(user); bool user_has_permissions = check_user_perm(user); if (num_sessions > MAX_ALLOWED_SESSIONS_PER_USER) { if (user_has_permissions) { // 自动关闭最旧的会话 char *oldest_session = get_oldest_session(user); if (oldest_session != NULL && kill_session(oldest_session) == 0) { pam_free(pamh, oldest_session); return PAM_SUCCESS; } else { pam_free(pamh, oldest_session); return PAM_SESSION_ERR; } } else { return PAM_SESSION_ERR; } } else { return PAM_SUCCESS; } }
3. 手动绑定对话函数(不推荐)
部分PAM服务允许在会话阶段保留对话上下文,但需手动设置PAM_CONV项,兼容性极差,仅适用于特定场景:
// 仅在少数特定服务下可能生效,不具备通用性 struct pam_conv *conv; res = pam_get_item(pamh, PAM_CONV, (const void **)&conv); if (res == PAM_SUCCESS && conv != NULL) { // 确保对话函数仍可用 res = pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &session_id, "...", NULL); }
内容的提问来源于stack exchange,提问作者Adrian J.
相关产品推荐
相关产品推荐

