基于PAM的C应用如何正确获取用户会话锁定状态及剩余解锁时间?
好问题!我之前在处理PAM结合faillock的认证场景时,也碰到过和你一模一样的痛点——pam_authenticate()只在本次尝试触发锁定时才返回PAM_MAXTRIES,完全感知不到用户之前已经被锁的情况。结合你的线程约束(只能在认证线程调用PAM接口)和不愿修改现有糟糕代码的诉求,给你几个优先级从高到低的可行方案:
1. 优先使用pam_acct_mgmt()检查账户状态
这是最符合PAM设计规范的方案,不需要碰任何外部文件或命令,完全通过PAM原生接口实现。
pam_authenticate()的职责是验证用户凭证的正确性,而pam_acct_mgmt()的职责是检查账户的可用性状态——包括是否被faillock锁定、账户是否过期等。你只需要在认证线程中,在pam_start()之后、pam_authenticate()之前(或之后)调用这个函数:
int ret; // 初始化PAM句柄后 ret = pam_acct_mgmt(pamh, 0); if (ret == PAM_LOCKED) { // 账户已被faillock锁定 // 这里可以处理锁定逻辑 } else if (ret != PAM_SUCCESS) { // 其他账户状态错误(如账户过期等) }
注意:要确保你的PAM配置(比如
/etc/pam.d/[你的服务名])的account阶段包含了pam_faillock.so模块,比如添加一行:account required pam_faillock.so
只有这样,pam_acct_mgmt()才会去检查faillock的锁定状态。
这个方案的优势是完全遵循PAM的设计,不需要依赖外部工具或解析不稳定的文件格式,而且所有操作都可以在你的认证线程内完成,不需要修改其他线程的代码。
2. 调用faillock命令行工具解析输出
如果因为某些原因无法使用pam_acct_mgmt()(比如PAM配置无法修改),可以通过调用系统自带的faillock命令来获取用户状态。这个工具是专门用来管理faillock锁定记录的,它的输出格式比直接解析/run/faillock下的文件稳定得多。
你可以在认证线程中用popen()执行命令并解析输出:
FILE *fp; char buf[256]; char cmd[128]; snprintf(cmd, sizeof(cmd), "faillock --user %s", username); fp = popen(cmd, "r"); if (fp == NULL) { // 处理命令执行错误 } while (fgets(buf, sizeof(buf), fp) != NULL) { if (strstr(buf, "locked")) { // 账户已锁定 // 可以进一步解析剩余时间,比如查找包含"remaining"的字段 } } pclose(fp);
注意:执行
faillock需要足够的权限(通常是root用户,或者将程序加入faillock组),否则无法读取/run/faillock下的文件。
这个方案的好处是不需要修改任何PAM相关的核心代码,只需要在认证线程中增加一段调用外部命令的逻辑,对现有代码侵入极小。
3. 谨慎解析/run/faillock文件(不推荐)
虽然你提到文件格式不稳定,但如果前两个方案都不可行,这是最后的退路。/run/faillock下的每个文件对应一个用户,文件内容是二进制格式,包含锁定时间、失败次数等信息。不过因为格式没有官方文档,不同发行版或faillock版本可能会有变化,所以这个方案的兼容性很差,不建议长期使用。
总结
优先选择方案1,完全基于PAM原生接口,最可靠也最符合规范;如果方案1受限于PAM配置,方案2是次优选择,依赖系统工具但兼容性比直接解析文件好得多。
内容的提问来源于stack exchange,提问作者Ágatha Isabelle

