Уильям Стивенс - UNIX: взаимодействие процессов
16.4. Аутентификация
По умолчанию в запросе RPC не содержится информации о клиенте. Сервер отвечает на запрос клиента, не беспокоясь о том, что это за клиент. Это называется нулевой аутентификацией, или AUTH_NONE.
Следующий уровень проверки подлинности называется аутентификацией Unix, или AUTH_SYS. Клиент должен сообщить библиотеке RPC времени выполнения информацию о себе (имя узла, действующий идентификатор пользователя, действующий идентификатор группы, дополнительные идентификаторы группы) для включения в каждый запрос. Изменим программу из листинга 16.2 таким образом, чтобы она включала возможность осуществления аутентификации Unix. В листинге 16.7 приведен новый текст программы-клиента.
Листинг 16.7. Клиент, осуществляющий аутентификацию unix//sunrpc/square4/client.с
1 #include "unpipc.h"
2 #include "square.h"
3 int
4 main(int argc, char **argv)
5 {
6 CLIENT *cl;
7 square_in in;
8 square_out out;
9 if (argc != 3)
10 err_quit("usage: client <hostname> <integer-value>");
11 cl = Clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, "tcp");
12 auth_destroy(cl->cl_auth);
13 cl->cl_auth = authsys_create_default();
14 in.arg1 = atol(argv[2]);
15 if (squareproc_2(&in, &out, cl) != RPC_SUCCESS)
16 err_quit("%s", clnt_sperror(cl, argv[1]));
17 printf("result: %ldn", out.resl);
18 exit(0);
19 }
12-13 Эти строки были добавлены в данной версии программы. Сначала мы вызываем auth_destroy для удаления предыдущей аутентификационной информации, связанной с данным дескриптором клиента (то есть дескриптор нулевой аутентификации, создаваемый по умолчанию). Затем вызов authsys_create_default создает соответствующую аутентификационную структуру Unix и мы сохраняем ее в поле cl_auth структуры CLIENT. Оставшаяся часть клиента не претерпела изменений по сравнению с листингом 16.5.
В листинге 16.8 приведен текст процедуры сервера, измененный по сравнению с листингом 16.6. Мы не приводим текст процедуры square_prog_2_freeresult, которая не меняется.
Листинг 16.8. Процедура сервера, запрашивающая аутентификацию Unix//sunrpc/square4/server.c
1 #include "unpipc.h"
2 #include "square.h"
3 bool_t
4 squareproc_2_svc(square_in *inp, square_out *outp, struct svc_req *rqstp)
5 {
6 printf("thread %Id started, arg = %ld, auth = %dn",
7 pr_thread_id(NULL), inp->arg1, rqstp->rq_cred.oa_flavor);
8 if (rqstp->rq_cred.oa_flavor == AUTH_SYS) {
9 struct authsys_parms *au;
10 au = (struct authsys_parms *)rqstp->rq_clntcred;
11 printf("AUTH_SYS: host %s, uid %ld, gid %ldn",
12 au->aup_machname, (long) au->aup_uid, (long) au->aup_gid);
13 }
14 sleep(5);
15 outp->res1 = inp->arg1 * inp->arg1;
16 printf("thread %ld donen", pr_thread_id(NULL));
17 return(TRUE);
18 }
6-8 Теперь мы используем указатель на структуру svc_req, которая всегда передается в качестве одного из аргументов процедуры сервера:
struct svc_req {
u_long rq_prog; /* номер программы */
u_long rq_vers; /* номер версии */
u_long rq_proc; /* номер процедуры */
struct opaque_auth rq_cred:/* данные о клиенте */
caddr_t rq_clntcred; /* готовые данные (только для чтения) */
SVCXPRT *rq_xprt; /* транспортный дескриптор */
};
struct opaque_auth {
enum_t oa_flavor; /* flavor: константа AUTH_xxx */
caddr_t oa_base; /* адрес дополнительной аутентификационной информации */
u_int oa_length; /* не должно превосходить MAX_AUTH_BYTES */
};
Поле rq_cred содержит неформатированную информацию о клиенте, а его поле oa_flavor содержит целое число, определяющее тип аутентификации. Термин «неформатированная» означает, что библиотека не обработала информацию, на которую указывает oa_base. Но если тип идентификации относится к одному из поддерживаемых библиотекой, то в готовой информации о клиенте, на которую указывает rq_clntcred, содержится некоторая структура, соответствующая данному типу аутентификации. Программа выводит тип аутентификации и прове-9_12 ряет, соответствует ли он AUTH_SYS.
Для аутентификации Unix указатель на готовую информацию (rq_clntcred) указывает на структуру authsys_parms, содержащую информацию о клиенте:
struct authsys_parms {
u_long aup_time; /* время создания информации */
char *aup_machname; /* имя узла клиента */
uid_t aup_uid; /* действующий идентификатор пользователя */
gid_t aup_gid; /* действующий идентификатор группы */
u_int aup_len; /* количество элементов в aup_gids[] */
gid_t *aup_gidsl; /* дополнительные идентификаторы группы */
};
Мы получаем указатель на эту структуру и выводим имя узла клиента, его EUID и EGID.
Запустив сервер и один экземпляр клиента, посмотрим на выводимый сервером текст:
solaris % server
thread 1 started, arg = 44, auth = 1
AUTH_SYS: host solaris.kohala.com, uid 765, gid 870
thread 1 done
Аутентификация Unix используется редко, поскольку ее легко обойти. Мы можем легко построить собственные пакеты RPC, содержащие аутентификационную информацию в формате Unix, присвоив идентификатору пользователя и группы произвольные значения, и отправить их на сервер. Сервер никак не может проверить, те ли мы, кем представляемся.
ПРИМЕЧАНИЕ
Вообще-то NFS по умолчанию использует именно аутентификацию Unix, но запросы обычно отсылаются ядром клиента NFS через зарезервированный порт (раздел 2.7 [24]). Некоторые серверы NFS настроены так, чтобы отвечать только на запросы, поступающие по зарезервированному порту. Если вы доверяете узлу клиента подключение к своим файловым системам, вы доверяете и его ядру в том, что оно правильно предоставляет информацию о своих пользователях. Если сервер не требует подключения по резервному порту, хакеры могут написать свою собственную программу, которая будет посылать запросы серверу NFS с произвольным идентификатором пользователя. Даже если сервер требует подключения по зарезервированному порту, а у вас есть своя система, в которой вы обладаете правами привилегированного пользователя, и вы можете подключиться к сети, вы сможете отправлять свои собственные запросы с произвольным содержимым на сервер NFS.
Пакеты RPC — как запросы, так и ответы — содержат два поля, относящиеся к аутентификации: данные о пользователе и проверочную информацию (credentials, verifier). Примером такой структуры является документ с фотографией (паспорт, права и т. п.). Данные о пользователе соответствуют написанному в паспорте тексту (имя, адрес, дата рождения и т. п.), а проверочная информация — это фотография. Существуют разные формы проверочной информации: фотография в данном случае полезнее, чем, например, рост, вес и пол. Если документ не содержит проверочной информации (как, например, читательский билет в библиотеке), любой может воспользоваться им и сказать, что он его владелец.
В случае нулевой аутентификации пакеты не содержат ни данных о пользователе, ни проверочной информации. В режиме аутентификации Unix данные о пользователе содержат имя узла, идентификаторы пользователя и группы, но поле проверочной информации пусто. Поддерживаются, однако, и другие формы аутентификации, для которых эти два поля содержат другую информацию.
■ AUTH_SHORT — альтернативная форма аутентификации Unix, отправляемая сервером в поле verifier в ответ на запрос клиента. Она содержит меньшее количество информации, чем в режиме аутентификации Unix, и клиент может отсылать ее серверу при последующих запросах. Используется для уменьшения количества передаваемой по сети информации.
■ AUTH_DES — аббревиатура DES означает Data Encryption Standard (стандарт шифрования данных). Эта форма аутентификации основана на использовании криптографии с секретным и открытым ключом. Эта схема также называется защищенным RPC (secure RPC), а если она используется в качестве основы для построения NFS, то такая NFS также называется защищенной.
■ AUTH_KERB — эта схема основана на стандарте Kerberos института MIT.
В главе 19 книги [5] подробно рассказывается о двух последних формах аутентификации, включая их настройку и использование.
16.5. Тайм-аут и повторная передача
Рассмотрим стратегию обработки тайм-аутов и повторной передачи, используемую в средствах Sun RPC. Существуют два значения тайм-аутов:
1. Общий тайм-аут определяет время ожидания ответа сервера клиентом. Это значение используется протоколами TCP и UDP.
2. Тайм-аут повтора используется только UDP и определяет время ожидания между повторами запросов клиента, если ответ от сервера не приходит.
Для протокола TCP необходимость во введении тайм-аута повтора отсутствует, поскольку этот протокол является надежным. Если сервер Не получает запроса от клиента, время ожидания по протоколу TCP со стороны клиента закончится и клиент повторит передачу. Когда сервер получает запрос клиента, он уведомляет об этом последний. Если уведомление о получении будет утрачено по пути к клиенту, тот должен будет еще раз переслать запрос. Повторные запросы сбрасываются сервером, но уведомления об их получении отсылаются клиенту. В надежных протоколах правильность доставки (время ожидания, повторная передача, обработка лишних копий данных и лишних уведомлений) обеспечивается на транспортном уровне и не входит в задачи библиотеки RPC. Один запрос, отправленный клиентом на уровне RPC, будет получен сервером ровно в одном экземпляре на уровне RPC. В противном случае клиент RPC получит сообщение о невозможности связаться с сервером. При этом совершенно не важно, что происходит на сетевом и транспортном уровнях.