Аутентификация PAM для устаревшего приложения

У меня есть устаревшее приложение, которое асинхронно получает запрос имени пользователя/пароля по сети. Поскольку у меня уже есть имя пользователя и пароль, сохраненные в виде переменных, как лучше всего пройти аутентификацию с помощью PAM в Linux (Debian 6)?

Я пытался написать свою собственную функцию разговора, но я не уверен, как лучше всего ввести в нее пароль. Я подумал о том, чтобы сохранить его в appdata и сослаться на него из структуры pam_conv, но почти нет документации о том, как это сделать.

Есть ли более простой способ аутентификации пользователей без излишних функций разговора? Я также не могу успешно использовать pam_set_data, и я не уверен, что это даже уместно.

Вот что я делаю:

user = guiMessage->username;
pass = guiMessage->password;

pam_handle_t* pamh = NULL;
int           pam_ret;
struct pam_conv conv = {
  my_conv,
  NULL
};

pam_start("nxs_login", user, &conv, &pamh);
pam_ret = pam_authenticate(pamh, 0);

if (pam_ret == PAM_SUCCESS)
  permissions = 0xff;

pam_end(pamh, pam_ret);

И первоначальные попытки функции разговора привели к (пароль жестко запрограммирован для тестирования):

int 
my_conv(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *data)
{
  struct pam_response *aresp;

  if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)
    return (PAM_CONV_ERR);
  if ((aresp = (pam_response*)calloc(num_msg, sizeof *aresp)) == NULL)
    return (PAM_BUF_ERR);
  aresp[0].resp_retcode = 0;
  aresp[0].resp = strdup("mypassword");

  *resp = aresp;
  return (PAM_SUCCESS);
}

Любая помощь будет оценена по достоинству. Спасибо!


person Jim Miller    schedule 06.05.2011    source источник


Ответы (4)


Это то, что я в итоге сделал. См. комментарий, отмеченный тремя звездочками.

#include <stdlib.h>
#include <iostream>
#include <fstream>
#include <security/pam_appl.h>
#include <unistd.h>

// To build this:
// g++ test.cpp -lpam -o test

// if pam header files missing try:
// sudo apt install libpam0g-dev

struct pam_response *reply;

//function used to get user input
int function_conversation(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr)
{
  *resp = reply;
  return PAM_SUCCESS;
}

int main(int argc, char** argv)
{
  if(argc != 2) {
      fprintf(stderr, "Usage: check_user <username>\n");
      exit(1);
  }
  const char *username;
  username = argv[1];

  const struct pam_conv local_conversation = { function_conversation, NULL };
  pam_handle_t *local_auth_handle = NULL; // this gets set by pam_start

  int retval;

  // local_auth_handle gets set based on the service
  retval = pam_start("common-auth", username, &local_conversation, &local_auth_handle);

  if (retval != PAM_SUCCESS)
  {
    std::cout << "pam_start returned " << retval << std::endl;
    exit(retval);
  }

  reply = (struct pam_response *)malloc(sizeof(struct pam_response));

  // *** Get the password by any method, or maybe it was passed into this function.
  reply[0].resp = getpass("Password: ");
  reply[0].resp_retcode = 0;

  retval = pam_authenticate(local_auth_handle, 0);

  if (retval != PAM_SUCCESS)
  {
    if (retval == PAM_AUTH_ERR)
    {
      std::cout << "Authentication failure." << std::endl;
    }
    else
    {
      std::cout << "pam_authenticate returned " << retval << std::endl;
    }
    exit(retval);
  }

  std::cout << "Authenticated." << std::endl;

  retval = pam_end(local_auth_handle, retval);

  if (retval != PAM_SUCCESS)
  {
    std::cout << "pam_end returned " << retval << std::endl;
    exit(retval);
  }

  return retval;
}
person Fantius    schedule 11.05.2011
comment
Спасибо! И здесь я пытался найти способы общаться в функции диалога вместо того, чтобы просто обходить и работать с ответом вне ее. - person Jim Miller; 12.05.2011
comment
Это не работает для «root» (просто «root», все остальные пользователи авторизованы нормально). Это ошибка? - person alexandernst; 28.04.2013
comment
Я не знаю. рут не пробовал. Вероятно, вы делаете что-то не так, если вам нужно регулярно использовать пароль root. - person Fantius; 28.04.2013
comment
Вы можете и, возможно, даже должны использовать appdata. Это произвольный указатель, который вы передаете PAM, а затем PAM возвращает его вам в обратных вызовах функции диалога. Таким образом, вы можете передавать любые произвольные данные. Это может быть указатель на функцию getpass, или на строку, которую вы передаете getpass, или на сам пароль. Что бы ни. Суть в том, что это позволяет вам избежать переменных, которые должны быть глобальными для совместного использования между вашей функцией и функцией диалога. - person Adam Badura; 29.09.2015
comment
Это решение приведет к чтению за пределами кучи, если какой-либо модуль PAM попытается обработать несколько сообщений в одном вызове функции диалога! Это редко, но может случиться. В этом случае ожидается, что *resp будет массивом из num_msg pam_response структур, а не указателем на одну структуру. - person cg909; 29.01.2021

Стандартная информация (например, пароль) передается для PAM с помощью переменных, установленных в дескрипторе pam с помощью pam_set_item (см. справочную страницу для pam_set_item).

Вы можете указать все, что вашему приложению понадобится позже, в файле pam_stack. Если вы хотите поместить пароль в pam_stack, вы сможете сделать это сразу после вызова pam_start(), установив переменную PAM_AUTHTOK в стек, подобно псевдокоду ниже:

pam_handle_t* handle = NULL;
pam_start("common-auth", username, NULL, &handle);
pam_set_item( handle, PAM_AUTHTOK, password);

Это сделает пароль доступным в стеке для любого модуля, который хочет его использовать, но обычно вы должны указать модулю использовать его, установив стандартные параметры use_first_pass или try_first_pass в pam_configuration для службы (в данном случае /etc /pam.d/common-auth).

Стандартный модуль pam_unix поддерживает try_first_pass, так что не мешало бы добавить его в конфигурацию pam в вашей системе (в конце строки для pam_unix).

После того, как вы сделаете это, любой вызов pam_authenticate(), который вызывается из службы общей аутентификации, должен просто выбрать пароль и использовать его.

Небольшое замечание о разнице между use_first_pass и try_first_pass: они оба сообщают модулю (в данном случае pam_unix) попробовать пароль в pam_stack, но их поведение отличается, когда пароль/AUTHTOK недоступен. В отсутствующем случае use_first_pass терпит неудачу, а try_first_pass позволяет модулю запрашивать пароль.

person John Bowers    schedule 28.08.2012
comment
pubs.opengroup.org/onlinepubs/8329799/ упоминает, что PAM_AUTHTOK доступно только для модулей PAM, но не для приложений. Таким образом, вы не должны быть в состоянии сделать то, что вы описали. Вы проверили, работает ли он? Может быть, это так, но не переносимо? - person Adam Badura; 29.09.2015
comment
Затем снова pam_set_item документация (pubs.opengroup.org/onlinepubs/8329799/), похоже, не повторяет это условие, хотя и предлагает PAM_AUTHOK. - person Adam Badura; 29.09.2015

Решение Fantius сработало для меня даже с правами root.

Первоначально я выбрал решение Джона, так как оно было чище и использовало переменные PAM без функции диалога (на самом деле, здесь в этом нет необходимости), но оно не работало и не будет работать. Как упоминал Адам Бадура в обоих сообщениях, PAM имеет некоторые внутренние проверки, чтобы предотвратить прямую установку PAM_AUTHTOK.

Решение Джона приведет к поведению, аналогичному тому, что упомянуто здесь, где любое значение пароля будет разрешено для входа в систему (даже если вы объявлять, но не определять переменную pam_conv).

Я бы также порекомендовал пользователям знать о размещении malloc, так как оно, скорее всего, будет отличаться в вашем приложении (помните, приведенный выше код больше похож на тест/шаблон, чем на что-либо еще).

person gagan    schedule 13.02.2018
comment
спасибо, что напомнили мне, почему я редко посещаю такие сайты;) приношу свои извинения! - person gagan; 13.02.2018

struct pam_conv {
    int (*conv)(int num_msg, const struct pam_message **msg,
                struct pam_response **resp, void *appdata_ptr);
    void *appdata_ptr;
};

Второе поле (appdata_ptr) структуры pam_conv передается функции диалога, поэтому мы можем использовать его в качестве указателя пароля.

     static int convCallback(int num_msg, const struct pam_message** msg,
                             struct pam_response** resp, void* appdata_ptr)
     {
            struct pam_response* aresp;
        
            if (num_msg <= 0 || num_msg > PAM_MAX_NUM_MSG)
                return (PAM_CONV_ERR);
            if ((aresp = (pam_response*)calloc(num_msg, sizeof * aresp)) == NULL)
                return (PAM_BUF_ERR);
            aresp[0].resp_retcode = 0;
            aresp[0].resp = strdup((char*)appdata_ptr);
                                                    
            *resp = aresp;
            
            return (PAM_SUCCESS);
    }

    int main()
    {
        ....
        pam_handle_t* pamH = 0;
        char *password = strdup("foopassword");
        struct pam_conv conversation = {convCallback, password};
        int retvalPam = pam_start("check_user", "foousername", &conversation, &pamH);
        
        //Call pam_authenticate(pamH, 0)
        //Call pam_end(pamH, 0);
        ...
        ...
        free(password);
    }
person Wendell Tabat    schedule 20.10.2020