Книга: Разработка приложений в среде Linux. Второе издание

28.2.2. Действия РАМ

28.2.2. Действия РАМ

РАМ не хранит никакой статической информации в библиотеке между вызовами, а хранит всю постоянную информацию с использованием непрозрачной структуры данных, передаваемой всем вызовам. Этот непрозрачный объект, pam_handle, управляется РАМ, однако хранится в вызывающем приложении. Он инициализируется с помощью функции pam_start() и освобождается функцией pam_end().

#include <security/pam_appl.h>
int pam_start (const char * service_name, const char * user,
 const struct pam_conv * pam_conversation,
 pam_handle_t ** pamh);
int pam_end(pam_handle_t * pamh, int pam_status);

Аргумент service_name должен представлять уникальное имя для вашего приложения. Это уникальное имя позволяет администратору системы конфигурировать защиту применительно к вашему приложению; два приложения, использующие одно и то же имя service name, разделяют одну и ту же конфигурацию. Аргумент user представляет имя аутентифицированного пользователя. Аргумент pam_conversation представляет структуру диалога, о которой мы уже говорили. Аргумент pamh представляет непрозрачный объект, который следит за внутренним состоянием. Функция pam_start() показана в строке 97 кода.

Функция pam_end(), показанная в строке 137 кода pamexample.с, очищает каждое; состояние, хранящееся в непрозрачном объекте pamh, и информирует модули, на которые он ссылается, о конечном состоянии действий. Если приложение успешно использовало РАМ, оно должно присвоить pam_status значение PAM_SUCCESS; в противном случае оно должно предоставить самую последнюю ошибку, возвращенную РАМ.

Бывают ситуации, когда модули РАМ могут использовать дополнительную информацию при принятии решения об аутентификации пользователя; эту информацию предоставляет система, а не пользователь. Кроме того, в некоторых случаях модули РАМ должны посылать приложению предупреждение об изменениях. Механизмом передачи этой дополнительной информации является элемент РАМ. Значение элемента задается с помощью функции pam_set_item(), а его запрос осуществляется функцией pam_get_item().

#include <security/pam_appl.h>
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);

Аргумент item_type определяет идентичность и семантику элемента РАМ item. Мы рассмотрим только наиболее часто используемые значения item_type.

PAM_TTY item представляет указатель на строку, содержащую имя устройства TTY, с которым связан запрос аутентификации. Это может быть tty59 для первого последовательного порта в стандартной системе или pts/0 для первого псевдотерминала, или tty1 для первой виртуальной консоли.
PAM_USER Функция pam_start() автоматически присваивает это значение аргументу user, переданному функции pam_start(). Важно отметить, что это имя может изменяться! Если вашему приложению нужно имя пользователя, то оно должно проверять значение посредством функции pam_get_item() после попадания в стек РАМ и перед производством изменения имени в другом коде.
PAM_RUSER Для сетевых протоколов (например, rsh и ssh) этот элемент должен применяться для передачи имени пользователя удаленной системы любым модулям РАМ, которые используют его. Благодаря этому администратор системы сможет определить, разрешена ли аутентификация типа rhost.
PAM_RHOST Подобно PAM_RUSER, PAM_RHOST необходимо задавать для сетевых протоколов, в которых имя удаленного хоста может использоваться как компонент аутентификации, или при управлении учетной записью.

Все остальные функции принимают по два аргумента: непрозрачный объект pamh и целое число, которое может быть либо нулевым, либо флагом РАМ_SILENT. Флаг PAM_SILENT требует, чтобы каркас и модули РАМ не генерировали информационных сообщений, однако при этом будет производиться запрос на ввод пароля. В обычных приложениях флаг РАМ_SILENT не задается.

Функция pam_authenticate(), показанная в строке 100 кода pamexample.с, выполняет все, что было сконфигурировано администратором системы в данном приложении (что определяется аргументом service_name функции pam_start()) для аутентификации пользователя. Сюда может быть включено следующее: запрос на ввод одного или нескольких паролей; проверка, что пользователь с текущим именем пользователя (что определяется по элементу РАМ РАМ_USER, а не по текущему uid; модули РАМ не рассматривают uid, поскольку приложения, вызывающие РАМ, обычно явным образом не выполняются после аутентификации пользователя) является текущим пользователем консоли; проверка, что текущий пользователь (снова по имени пользователя) недавно прошел аутентификацию для эквивалентного уровня обслуживания; проверка элементов РАМ PAM_RUSER и PAM_RHOST в отношении локальных таблиц эквивалентных удаленных пользователей и хостов (например, то, что выполняет демон rsh), или что-либо подобное. (Обратите внимание, что в большинстве систем задействована система "теневых паролей", при котором с целью защиты пароля только процессы с полномочиями root могут проверять пароли произвольных пользователей; процесс, который не выполняется как root, может проверять только собственный пароль uid. Это единственное исключение из правила, когда модули РАМ принимают во внимание uid.)

Функция pam_acct_mgmt(), показанная в строке 107, предполагает, что функция pam_authenticate() уже была вызвана и продолжает свою работу, а затем проверяет (также по имени), разрешено ли пользователю осуществлять запрошенный доступ. Она может рассматривать элементы РАМ PAM_USER, PAM_TTY, PAM_RUSER и PAM_RHOST, определяя разрешение на доступ. Например, какому-то одному пользователю могут быть разрешены некоторые tty только в течение определенных часов, или каждому пользователю в некотором классе может быть разрешено только некоторое количество параллельных регистраций, или некоторым совокупностям удаленных пользователей и хостов могут быть даны разрешения только на несколько часов.

Функция pam_setcred(), показанная в строке 118, предполагает, что аутентификация была пройдена, и затем устанавливает сертификаты для пользователя. Хотя uid, gid и дополнительные группы технически являются сертификатами, они не находятся под управлением функции pam_setcred(), поскольку во многих приложениях это не будет соответствовать модели безопасности. Наоборот, она устанавливает дополнительные сертификаты. Вероятным кандидатом для использования в качестве сертификата является мандат Kerberos — файл, содержащий зашифрованные данные, которые предоставляют пользователю разрешение на доступ к некоторым ресурсам.

Функция pam_open_session(), показанная в строке 113, открывает новый сеанс. Если в процессе есть ветвления, то функцию pam_open_session() необходимо вызывать после ветвления, поскольку при этом могут происходить такие действия, как установка rlimits (см. стр. 120–121). Если ваш процесс запускается как root, а затем изменяется на uid аутентифицированного пользователя, функцию pam_open_session() нужно вызывать перед сбросом привилегий root, поскольку модули сеанса могут попытаться выполнить системные операции (например, монтирование домашних каталогов), которые зарезервированы только для root.

Функция pam_close_session(), показанная в строке 128, закрывает существующий сеанс. Ее можно вызывать из другого процесса, а не только из того, который вызвал функцию pam_open_session(), при том условии, что РАМ будут доступны одни и те же данные — те же аргументы, заданные для РАМ, и те же элементы РАМ, которые используются для открытия сеанса. Обратите внимание, что поскольку элемент PAM_USER может быть изменен во время аутентификации и управления учетной записью, вы должны убедиться, что производится учет любых изменений, производимых с PAM_USER, если вы вызываете ее из отдельного процесса, установившего сеанс. Для работы функции pam_close_session() могут потребоваться привилегии root.

  1: /* pamexample.с */
  2:
  3: /* Программа pamexample демонстрирует вариант простой обработки РАМ.
  4:  * Вам нужно будет либо использовать параметр командной строки —service
  5:  * для выбора имени уже установленной службы (может работать "system-auth",
  6:  * проверьте наличие /etc/pam.d/system-auth в своей системе), либо
  7:  * установки системного файла */etc/pam.d/pamexample со следующими
  8:  * четырьмя строками (игнорируя ведущие символы "*") :
  9:  * #%РАМ-1.0
 10:  * auth required /lib/security/pam_unix.so
 11:  * account required /lib/security/pam_unix.so
 12:  * session required /lib/security/pam_limits.so
 13:  *
 14:  * Обратите внимание, что если вы запустите эту программу не как root, то
 15:  * можете столкнуться с ограничениями системы; при управлении учетными
 16:  * записями может произойти сбой, вам может быть не разрешено проверять
 17:  * другие пароли пользователей, в управлении сеансом может произойти
 18:  * сбой - все будет зависеть от того, как сконфигурирована служба.
 19:  */
 20:
 21: #include <security/pam_appl.h>
 22: #include <security/pam_misc.h>
 23: #include <popt.h>
 24: #include <pwd.h>
 25: #include <sys/types.h>
 26: #include <stdio.h>
 27: #include <stdlib.h>
 28: #include <unistd.h>
 29:
 30: /* Эта структура может быть автоматической, но она не должна выходить
 31:  * за пределы между функциями pam_start() и pam_end(), поэтому в простых
 32:  * программах легче всего сделать ее статической.
 33:  */
 34:  static struct pam_conv my_conv = {
 35:   misc_conv, /* использование функции диалога TTY из libpam_misc */
 36:   NULL /* у нас нет специальных данных для передачи в misc_conf */
 37:  };
 38:
 39:  void check_success(pam_handle_t * pamh, int return_code) {
 40:   if (return_code != PAM_SUCCESS) {
 41:    fprintf (stderr, '"%sn", pam_strerror(pamh, return_code));
 42:    exit(1);
 43:   }
 44:  }
 45:
 46:  int main(int argc, const char ** argv) {
 47:   pam_handle_t * pamh;
 48:   struct passwd * pw;
 49:   char * username=NULL, * service=NULL;
 50:   int account = 1, session = 0;
 51:   int c;
 52:   poptContext optCon;
 53:   struct poptOption optionsTable[] = {
 54:    { "username", 'u', POPT_ARG_STRING, &username, 0,
 55:      "Имя пользователя для аутентификации", "<имя_пользователя>" },
 56:    { "service", 'S', РОPT_ARG_STRING, &service, 0,
 57:      "Имя службы для инициализации как (pamsample)",
 58:      "<служба>" },
 59:    { "account", 'a', POPT_ARG_NONE|POPT_ARGFLAG_XOR,
 60:      &account, 0,
 61:      "включение/выключение управления учетными записями (включено)", "" },
 62:    { "session", 's', POPT_ARG_NONE|POPT_ARGFLAG_XOR,
 63:      &session, 0,
 64:      "включение/выключение запуска сеанса (выключено)", "" },
 65:    POPT_AUTOHELP
 66:    POPT_TABLEEND
 67:   };
 68:
 69:   optCon = poptGetContext("pamexample", argc, argv,
 70:    optionsTable, 0);
 71:   if ((c = poptGetNextOpt(optCon)) < -1) {
 72:    fprintf(stderr, "%s: %sn",
 73:    poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
 74:    poptStrerror(c));
 75:    return 1;
 76:   }
 77:   poptFreeContext(optCon);
 78:
 79:   if (!service) {
 80:    /* Обратите внимание, что обычное приложение не должно предоставлять
 81:     * этот параметр пользователю; он присутствует здесь, чтобы можно было
 82:     * проверить это приложение, не производя изменений в системе,
 83:     * требующих доступа root.
 84:     */
 85:   service = "pamexample";
 86:  }
 87:
 88:  if (!username) {
 89:   /* по умолчанию для текущего пользователя */
 90:   if (!(pw = getpwuid (getuid())) ) {
 91:    fprintf(stderr, "Имя пользователя не существует");
 92:    exit(1);
 93:   }
 94:   username = strdup(pw->pw_name);
 95:  }
 96:
 97:  с = pam_start(service, username, &my_conv, &pamh);
 98:  check_success(pamh, c);
 99:
100:  с = pam_authenticate(pamh, 0);
101:  check_success(pamh, c);
102:
103:  if (account) {
104:   /* если аутентификация не была закончена, управление
105:    * учетной записью не определено
106:    */
107:   с = pam_acct_mgmt(pamh, 0);
108:   check_success(pamh, с);
109:  }
110:
111:  if (session) {
112:   /* В случае необходимости мы могли бы организовывать здесь ветвление */
113:   с = pam_open_session(pamh, 0);
114:   check_success(pamh, с);
115:
116:   /* Обратите внимание, что здесь не устанавливается uid, gid
117:      или дополнительные группы */
118:   с = pam_setcred(pamh, 0);
119:
120:   /* В случае необходимости мы могли бы закрыть здесь полномочия */
121:
122:   /* Вызов оболочки, которая была "аутентифицирована" */
123:   printf("Запуск оболочки...n");
124:   system("exec bash -");
125:
126:   /* Здесь мы могли бы использовать wait4(), если бы организовывали
127:      ветвление вместо вызова system() */
128:   с = pam_close_session(pamh, 0);
129:   check_success(pamh, с);
130:  }
131:
132:  /* Реальные приложения могли бы сообщать о сбое вместо
133:   * выхода, что мы и делали в check_success на каждой стадии,
134:   * поэтому в таких случаях с может иметь значения, отличные
135:   * от PAM_SUCCESS.
136:   */
137:  с = pam_end(pamh, с);
138:  check_success(pamh, с);
139:
140:  return 0;
141: }

Оглавление книги

Оглавление статьи/книги

Генерация: 0.133. Запросов К БД/Cache: 0 / 0
поделиться
Вверх Вниз