如何处理pam_authenticate对有效用户和密码的PAM_AUTHTOK_RECOVERY_ERR返回值

How to handle PAM_AUTHTOK_RECOVERY_ERR return value from pam_authenticate for valid user and password?

本文关键字:密码 PAM AUTHTOK 返回值 ERR RECOVERY 用户 何处理 处理 authenticate pam      更新时间:2023-10-16

我正在尝试编写一些使用linux pam对客户端进行身份验证的服务器。我写了下面的类:

class Pam
{
public:
    Pam(const char *module, const char *username)
    {
        mConv.appdata_ptr = nullptr;
        mConv.conv = &convCallback;
        const int res = pam_start("system-auth", username, &mConv, &mPamHandle);
        if (res != PAM_SUCCESS)
            throw std::runtime_error("Failed to initialize PAM");
     }
     bool authenticate(char *passwd)
     {
         pam_response *resp = static_cast<pam_response*>(malloc(sizeof(pam_response)));
         resp->resp = passwd;
         resp->resp_retcode = 0;
         mConv.appdata_ptr = resp;
         const int res = pam_authenticate(mPamHandle, 0);
         log(res);
         return res == PAM_SUCCESS;
    }
    ~Pam()
    {
        if (mPamHandle)
            pam_end(mPamHandle, PAM_SUCCESS);
        mPamHandle = nullptr;
    }
private:
    static int convCallback (int msgId, const pam_message **msg, pam_response **resp, void *appData)
    {
        *resp = static_cast<pam_response*>(appData);
        return PAM_SUCCESS;
    }
private:
    pam_handle_t *mPamHandle = nullptr;
    pam_conv mConv;
};

它的用法如下:

Pam pam("system-auth", username);
if (pam.authenticate(passwd))
    return true;
// error handling code here

我发现pam_authenticate对于有效的用户/密码返回PAM_AUTHTOK_RECOVERY_ERR。手册页和linuxpam.org http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_authenticate中记录的可能返回值根本不包含此值。文档说它可以通过pam_chauthtok返回,这意味着:

PAM_AUTHTOK_RECOVERY_ERR

一个模块无法获得旧的认证令牌。

目前还不清楚它在身份验证的情况下意味着什么。我已经尝试运行代码作为普通用户和根的结果是一样的。

正在发生的事情是,您将0视为convCallbackappData的值,这就是错误的来源-回复数据为空,这意味着不良对话,这导致PAM_AUTHTOK_RECOVERY_ERR返回值。这是基于读取PAM-Linux源代码的当前代码中的support.c文件。

好,有几个问题。

  1. 你不能在初始化后重新分配会话appdata_ptr值-指针的值应该在调用pam_start后被视为常量。你应该在那里传递一个永远不会改变的值。如果您检查了对话功能,您会注意到appData的值是0

  2. 必须假设被放入回复中的值是由调用例程拥有的-也就是说,你必须将密码字符串(与所有与之相关的邪恶)串联起来。

考虑到这两种情况,我将代码稍微修改如下,这应该可以解决您的问题(同样,这是简化的代码):
class Pam
{
public:
    Pam(const char *module, const char *username)
    {
        mConv.appdata_ptr = (void *)(this);
        mConv.conv = &convCallback;
        const int res = pam_start(module, username, &mConv, &mPamHandle);
        if (res != PAM_SUCCESS)
            throw std::runtime_error("Failed to initialize PAM");
     }
     bool authenticate(char *passwd)
     {
         mPassword = passwd;
         const int res = pam_authenticate(mPamHandle, 0);
         log(res);
         return res == PAM_SUCCESS;
    }
    ~Pam()
    {
        if (mPamHandle)
            pam_end(mPamHandle, PAM_SUCCESS);
        mPamHandle = 0;
    }
private:
    static int convCallback (int msgId, const pam_message **msg, pam_response **resp, void *appData)
    {
        Pam *me = static_cast<Pam *>(appData);
        pam_response *reply = static_cast<pam_response *>(calloc(1, sizeof(pam_response)));
        reply->resp = strdup(me->mPassword);
        reply->resp_retcode = 0;
        *resp = reply;
        return PAM_SUCCESS;
    }
private:
    pam_handle_t *mPamHandle = 0;
    pam_conv mConv;
    const char *mPassword = 0;
};