Наши партнеры

UnixForum





Библиотека сайта rus-linux.net

Разработка модулей PAM, часть 2

Оригинал: Writing PAM Modules, Part Two
Автор: Jennifer Vesperman
Дата публикации: 23 Мая 2013 г.
Перевод: А.Панин
Дата перевода: 20 Февраля 2013 г.

Аббревиатура PAM расшифровывается как Pluggable Authentication Modules (система подключаемых модулей аутентификации) и используется для обозначения системы, позволяющей приложениям осуществлять независимую аутентификацию. Приложение, работающее с PAM, использует стек модулей PAM для осуществления аутентификации, открытия и закрытия сессий и проверки работоспособности учетной записи.

Данная статья является второй частью серии из трех статей о разработке модулей PAM. В первой части была приведена необходимая для разработки модулей информация, а в третьей будут рассмотрены критические функции, которые должен предоставлять каждый модуль. В данной же статье приводятся описания функций, которые придется использовать автору модуля.

Пакеты и файлы

В данных статьях приводится описание приложения на языке C++, так как большинство масштабируемых приложений разрабатывается с применением именно этого языка и его отличия от языка C, хотя они и существуют, не являются значительными. Вы можете разрабатывать совместимые с системой Linux PAM модули, используя любой язык программирования в том случае, если он позволяет вам осуществлять вызовы необходимых функций языка C.

Для начала вам необходимо установить заголовочные файлы для разработки. В системе Debian это делается с помощью команды apt-get install libpam0g-dev. Эта команда позволяет установить необходимые файлы исходного кода, разместив их в тех директориях, где компилятор g++ сможет их найти.

Вам будет необходимо включить заголовочный файл в в файл исходного кода вашего модуля при помощи директивы #include <security/pam_modules.h>.

При компиляции вам будет необходимо осуществить связывание модуля с файлами библиотеки libpam. В моем файле makefile для этого используется строка: g++ -omodule_name -lpam sourcefile.

В случае статического связывания модуля необходимо использовать объявление четырех типов модулей с помощью директивы #define перед тем, как осуществить включение заголовочного файла security/pam_modules.h с помощью директивы #include.
#define PAM_SM_ACCOUNT
#define PAM_SM_AUTH
#define PAM_SM_PASSWORD
#define PAM_SM_SESSION

Установка и получение значений элементов PAM

Большая часть необходимой модулю информации может быть получена или передана с помощью элементов PAM. Эти элементы относятся к определенным сессиям PAM и идентифицируются с помощью хэндлов. Хэндл PAM передается каждой из основных функций модуля в качестве параметра.

Следует использовать функции pam_set_item() и pam_get_item() для передачи или получения данных с помощью элементов PAM.
extern int pam_set_item(pam_handle_t *pamh, int item_type, const void *item);
extern int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item);

Доступные типы элементов PAM могут быть разделены на группы.

Элементы, относящиеся к приложению:
PAM_SERVICE
Имя приложения в рамках системы PAM, не обязательно то имя приложения, которое видит пользователь.
PAM_CONV
Работа со структурой обмена данными (Смотрите следующий раздел).
PAM_FAIL_DELAY
Указатель на функцию PAM_FAIL_DELAY. Некоторые приложения производят замену версии данной функции из PAM.
Элементы, относящиеся к пользователю:
PAM_USER
Имя пользователя, под которым производится аутентификация. Обычно безопаснее использовать функцию pam_get_user(), чем осуществлять запрос имени напрямую.
PAM_USER_PROMPT
Приглашение, которое модуль должен использовать при запросе имени пользователя.
PAM_RUSER
Идентификатор пользователя, запрашивающего аутентификацию, обычно имя пользователя, вызвавшего приложение.
Элементы относящиеся к системе:
PAM_RHOST
Имя узла для системы, с которой происходит запрос аутентификации.
PAM_TTY
Имя терминала (для консольных приложений) или значение переменной $DISPLAY (для приложений с графическим интерфейсом). Вы можете получить имя терминала с помощью функции ttyname().
Элементы, относящиеся к процессу аутентификации:
PAM_AUTHTOK
Данные аутентификации. Элемент должен игнорироваться всеми модулями кроме модулей для аутентификации и смены пароля и использоваться только совместно с функциями pam_sm_authenticate() и pam_sm_chauthtok().
PAM_OLDAUTHTOK
Предыдущие данные аутентификации. Элемент должен использоваться только совместно с функцией pam_sm_chauthtok() в модулях для работы с паролями.

Кроме статуса PAM_SUCCESS, функции для работы с элементами PAM могут также возвращать статусы PAM_SYSTEM_ERR, PAM_PERM_DENIED, PAM_BUF_ERR или PAM_BAD_ITEM.

Функция обмена данными

Приложение может выводить сообщение, используя неограниченное количество форматов. Сообщения приложений с графическим интерфейсом разительно отличаются от сообщений приложений с интерфейсом командной строки, а оба этих вида сообщений в свою очередь отличаются от сообщений зашифрованной сессии HTTP или сессии FTP.

Система PAM освобождает разработчика от проблем форматирования сообщений, требуя от приложения реализации функции обмена данными (conversation function). Эта функция передает сообщения от модуля и знакомит пользователя с ними, а также передает ответы пользователя модулю.

Вызов функции pam_get_item(pamhandle, PAM_CONV, &item) позволяет получить указатель на структуру обмена данными. Эта структура состоит из указателя на функцию обмена данными conv и указателя appdata_ptr, который приложение может использовать для обмена данными с модулем.
struct pam_conv {
    int (*conv)(int num_msg,
        const struct pam_message **msg,
        struct pam_response **resp,
        void *appdata_ptr);
    void *appdata_ptr;
};

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

Модуль должен создать массив указателей на структуры pam_message, предназначенные для хранения строк сообщений, которые должны быть выведены приложением. Указателем на этот массив является параметр msg, при этом каждая структура содержит и сообщение и спецификатор форматирования сообщения. Параметр num_msg задает длину массива сообщений.
struct pam_message {
    int msg_style;
    const char *msg;
};
Доступные спецификаторы форматирования сообщений:
PAM_ERROR_MSG
Вывод сообщения об ошибке.
PAM_PROMPT_ECHO_OFF
Получение строки с подавлением вывода.
PAM_PROMPT_ECHO_ON
Получение строки без подавления вывода.
PAM_TEXT_INFO
Вывод сообщения.

Приложение должно предоставить массив структур pam_response в ответ на запрос. Модуль должен предоставить указатель, значение которого должно быть изменено приложением таким образом, чтобы он указывал на массив этих структур. Да, взаимодействие происходит с помощью запутанных путей. Указатель, используемый для этой цели модулем, представлен параметром resp.

Модуль должен присвоить значение NULL или другое проверяемое значение этому указателю перед вызовом функции обмена данными для того, чтобы убедиться в том, что приложение изменило его значение.
struct pam_response {
    char *resp;
    int resp_retcode;
};

Переменная resp_retcode в структуре ответа на данный момент не используется и обычно ее значение устанавливается равным 0.

Следует отметить, что указатель appdata_ptr является последним параметром функции - это важно. Приложение может использовать указатель appdata_ptr для передачи с помощью функции данных, которые ему могут потребоваться, поэтому убедитесь в том, что вы используете именно этот указатель.

Кроме статуса PAM_SUCCESS, функция обмена данными также может возвращать статус PAM_CONV_ERR.

Получение имени пользователя

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

Следует использовать функцию pam_get_user вместо того, чтобы выполнять эту работу самостоятельно. Это проще, понятнее при чтении кода и в случае изменения процесса получения имени пользователя, вам не придется делать ровным счетом ничего!
extern int pam_get_user(pam_handle_t *pamh, const char **user, const char *prompt);

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

Значение возвращаемого статуса PAM_SUCCESS очевидно. Другими возвращаемыми статусами являются PAM_CONV_ERR, указывающий на то, что имя пользователя не может быть получено, и PAM_CONV_AGAIN.

В том случае, если вы получили статус PAM_CONV_AGAIN, верните управлению приложению с помощью передачи статуса PAM_INCOMPLETE, а при следующем вызове функции pam_get_user() с данным хэндлом PAM вы сможете получить данные.

Сохранение и получение данных

Автор модуля не может предсказать то, как модуль будет загружен, поэтому вы должны избегать использования статических переменных. Функции для сохранения и получения данных позволяют ассоциировать данные с хэндлом PAM. Данные, сохраненные с помощью данной функции, будут доступны другим модулям, но не приложению.

Параметр cleanup позволяет задать специальную функцию корректного освобождения зарезервированной для хранения данных памяти. В случае, если такой функции не требуется, может использоваться значение NULL.

В качестве значения параметра error_status могут объединяться с помощью логического "ИЛИ" значения PAM_DATA_REPLACE для указания последующему вызову на необходимость замены данных и PAM_DATA_SILENT для того, чтобы функция освобождения ресурсов не оставляла записей в системном журнале и не отправляла сообщений пользователю.

Если данные сохранены, но указатель на данные имеет значение NULL, функция pam_get_data() должна вернуть PAM_NO_MODULE_DATA.

Управление переменными окружения

Система Linux-PAM поддерживает работу отдельного окружения, ассоциированного с текущим хэндлом PAM. После создания окружение не содержит переменных.

extern const char *pam_getenv(pam_handle_t *pamh, const char *name);
Возвращает значение заданной переменной окружения Linux-PAM или NULL в случае неудачи.
extern const char * const *pam_getenvlist(pam_handle_t *pamh);
Возвращает указатель на текущий список переменных окружения Linux-PAM, предназначенный только для чтения.
extern int pam_putenv(pam_handle_t *pamh, const char *name_value);
Позволяет установить сбросить или удалить заданную переменную окружения. Аргумент name_value является строкой с завершающим нулевым значением (в представлении языка C). Поддерживаемые форматы:
name=value
Устанавливает значение переменной 'name' равным 'value'
name=
Устанавливает значение переменной 'name' равным пустой строке
name
Удаляет переменную 'name'

Безопасная работа с памятью

Любые участки памяти, которые ваш модуль не будет впоследствии освобождать, должны резервироваться с помощью функции std::malloc() из стандартной библиотеки языка C. Операторы new и delete могут также использоваться, но только для участков памяти, которые вы будете освобождать самостоятельно.

Заключительные слова

Данная статья является второй частью серии из трех статей по разработке модулей PAM. В первой части рассматривались теоретические основы функционирования системы PAM и принципы работы, которые должны быть реализованы в рамках модуля. В третьей части серии приводится описание функций, которые обязан предоставлять каждый модуль, а также обсуждение вопросов безопасности и кодов ответов.

Дополнительные ресурсы