Я пытаюсь написать какой-то сервер, который проверяет подлинность клиентов с помощью 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 для действительного пользователя/пароля. Возможные возвращаемые значения, зарегистрированные на странице руководства и на linux-pam.org http://www.linux-pam.org/Linux-PAM-html/adg-interface-by-app-expected.html#adg-pam_authenticate do не содержат этого значения вообще. Документация говорит, что она может быть возвращена pam_chauthtok, и это означает:
PAM_AUTHTOK_RECOVERY_ERR
Модуль не смог получить старый токен аутентификации.
И еще неясно, что это означает в случае аутентификации. Я пытался запустить код как обычного пользователя, а как root, результат был таким же.
Что происходит, так это то, что вы видите 0
как значение appData
в convCallback
, откуда исходит ошибка - данные ответа пустые, что означает "плохой разговор", что приводит к возврату PAM_AUTHTOK_RECOVERY_ERR
. Это основано на чтении файла support.c
в текущем коде для исходного кода PAM-Linux.
Хорошо, пару вопросов.
После инициализации нельзя переназначить значение appdata_ptr для разговора - значение указателя следует считать константой после вызова pam_start
. Вы должны передать туда значение, которое никогда не изменится. Если вы проверили функцию разговора, вы заметили бы, что значение appData
равно 0
.
Вы должны предположить, что значение, которое помещается в ответ, принадлежит вызывающей процедуре, то есть вам придется strdup строку пароля (со всем злом, которое связано с этим).
Учитывая оба эти соображения, я немного изменил ваш код на следующее, что должно решить ваши проблемы (опять же, это упрощенный код):
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;
};
pam_unix
имеет параметрuse_first_pass
установленный вместоtry_first_pass
для аутентификации, и это первая запись в стеке PAM.auth required pam_unix.so try_first_pass nullok
иpassword required pam_unix.so try_first_pass nullok sha512 shadow
так что похоже, чтоtry_first_pass
используется по умолчанию