Шаг 13 - Получение информации о пользователе

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

Самые основные настройки для пользователя можно получить из переменных среды:

Получить переменные среды можно с помощью функции getenv():

#include <stdlib.h>

char *getenv(const char *name);

Данная функция ищет переменную среды с именем name и возвращает на нее указатель в случае удачи, иначе возвращает NULL.

#include <stdlib.h>

int main(){
    printf("USER = \'%s\'\n",getenv("USER"));
    printf("HOME = \'%s\'\n",getenv("HOME"));
    printf("PATH = \'%s\'\n",getenv("PATH"));
	return 0;
};

Для примера результат работы данной программы:

dron~# ./a.out
USER = 'root'
HOME = '/root'
PATH = '/usr/local/sbin:/usr/sbin:/usr/bin'

Но почему нельзя полагаться на эти переменные ? Да потому, что их можно легко изменить одной командой (или вызовом фукнции в программе):

dron~# export USER=""
dron~# export HOME="/yoyoyoy"
dron~# ./a.out
USER = ''
HOME = '/yoyoyoy'
PATH = '/usr/local/sbin:/usr/sbin:/usr/bin'

Получается, что если другая программа установит другие переменные среды, то Вы больше никогда ничего реального не узнаете.

Но не все так плохо, просто надо пользоваться информацией "из первых рук". Для того, чтобы получить идентификатор пользователя, который запустил программу существует специальный набор функций:

#include <unistd.h>
#include <sys/types.h>

uid_t getuid(void);
uid_t geteuid(void);

Функция getuid() (get user id) возвращает реальный идентификатор пользователя для текущего процесса, который установлен в соответствие идентификатору вызывающего процесса.

Функция geteuid() (get effective user id) возвращает эффективный идентификатор пользователя, который устанавливается в соответствии с битом set ID на запускаемом файле.

Давайте посмотрим как работают эти функции, для этого напишем простую программку test.c:

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>

int main(){

    printf ("Real User ID = %d\n",getuid());
    printf ("Effective User ID = %d\n\n",geteuid());

    return 0;
};

Теперь скомпилируем командой gcc test.c -o test и запустим. Я сейчас сижу под пользователем root. Посмотрим сначала что вышло в каталоге:

dron~# ls -l
total 20
-rwxr-xr-x    1 root     root        13500 Dec 22 04:45 test
-rw-r--r--    1 root     root          197 Dec 22 04:39 test.c

Как видите владелец обоих файлов является root. Теперь запускаем программу test:

dron~# ./test
Real User ID = 0
Effective User ID = 0

Так как программа была запущена из под root идентификатор пользователя равен 0. А как же насчет geteuid(), сейчас ее результат аналогичен работе getuid(). Давайте попробуем добиться того, чтобы этот идентификатор был другим. Как было написано выше эта функция возвращает идентификатор пользователя установленного на файле, да к томе же если на нем установлен бит set ID. Давайте сначала поменяем на файле пользователя и посмотрим, что выйдет из этого.

dron~# chown dron:users test
dron~# ls -l
total 20
-rwxr-xr-x    1 dron     users       13500 Dec 22 04:45 test
-rw-r--r--    1 root     root          197 Dec 22 04:39 test.c
dron~# ./test
Real User ID = 0
Effective User ID = 0

Интересно. Мы поменяли пользователя на dron, что вы можете увидеть из результата команды ls, однако как и следовало ожидать результат работы функции geteuid() остался таким же.

Теперь установим бит set ID на файл test:

dron~# chmod +s test
dron~# ls -l
total 20
-rwsr-sr-x    1 dron     users       13500 Dec 22 04:45 test
-rw-r--r--    1 root     root          197 Dec 22 04:39 test.c

Теперь, если вы сравните старые биты привилегий -rwxr-xr-x с новыми -rwsr-sr-x, то увидите вместо x букву s, а это означает, что бит set ID установлен. Теперь если мы запустим программу снова, то увидим другой результат:

dron~# ./test
Real User ID = 0
Effective User ID = 1000

Теперь мы поняли в чем смысл работы geteuid(), однако мне пока в голову не приходят мысли насчет ее полезности. Возможно так программа может отличать своего реального владельца от того, кто ее запускает. К примеру можно было бы в начале программы использовать следующий код:

if (getuid()!=geteuid()){
	printf("Вы не можете использовать чужую программу.\n");
	exit();
};

Тогда эту программу не сможет запустить никто кроме ее законного владельца. Незнаю насколько это "эффективно", может быть у Вас есть какие-то мысли по этому поводу. Я даже уверен, что Вы когда-нибудь ее примените в своей разработке :)

Ну, а теперь надо учиться что-то делать с этими идентификаторами. Наша начальная задача была получить настройки для пользователя, давайте этим и займемся. Откуда Вы можете достоверно получить информацию о пользователе ? Вы знаете ответ на этот вопрос ? Ну, конечно же из файла /etc/passwd.

Да да да, именно из этого файла, только не пугайтесь сразу того, что он текстовый и вам придется заниматься парсингом. Нет, не надо этим заниматься, все сделали до нас. Для этого существуют специальные функции:

#include <pwd.h>
#include <sys/types.h>

struct passwd *getpwnam(const char * name);
struct passwd *getpwuid(uid_t uid);

Первая функция getpwnam() возвращает информацию о пользователе по его имени name, вторая функция getpwuid() получает информацию о пользователе по идентификатору, который получать мы уже умеем :)

Обе данные функции возвращают информацию в виде заполненной структуры struct passwd:

struct passwd {
	char    *pw_name;       /* user name */
	char    *pw_passwd;     /* user password */
	uid_t   pw_uid;         /* user id */
	gid_t   pw_gid;         /* group id */
	char    *pw_gecos;      /* real name */
	char    *pw_dir;        /* home directory */
	char    *pw_shell;      /* shell program */
};

Как видите тут можно получить даже больше данных, чем через переменные среды. А, что мы хотели получить ? Ну, давайте к примеру выведем домашний каталог и командный интерпретатор (ака shell).

#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <pwd.h>

int main(){

    struct passwd *userinfo;
    uid_t userid;

    printf ("Real User ID = %d\n",userid = getuid());

    userinfo = getpwuid(userid);
    if (userinfo!=NULL){
        printf("user name = '%s'\n",userinfo->pw_name);
        printf("user home dir = '%s'\n",userinfo->pw_dir);
        printf("user shell = '%s'\n",userinfo->pw_shell);
    };

    return 0;
};

Результат работы программы будет выглядеть так:

dron~# ./a.out
Real User ID = 0
user name = 'root'
user home dir = '/root'
user shell = '/bin/bash'

У данной функции есть несомненный плюс - это, конечно же, способность возвратить самые точные системные настройки для требующегося пользователя. НО ! Посмотрите на возвращаемую структуру, вы видите что-нибудь интересное ? Ну, как же... Это поле pw_passwd, в котором содержится пароль пользователя, пусть и в зашифрованном виде. Получается, что любая программа запущенная под любым пользователем может получить пароль другого пользователя. В дальнейшем к примеру, этот пароль можно попробовать расшифровать и уже получить доступ под другим пользователем. А что может случиться, если этот атакуемый пользователь был root ?! Тогда возможна угроза полного взлома системы. Все разработчики системы Linux понимали это, и именно поэтому придумали технологию shadow passwords. В соответствии с данной технологией создается второй файл /etc/shadow с полной копией информации из /etc/passwd, а затем из файла passwd удаляются пароли(заменяются на символ x), а на shadow ставится доступ на чтение только пользователю root. Таким образом любая программа может получить всю информацию о пользователях, кроме их паролей. Сами же пароли может получить лишь программа запущенная в привилегированном режиме с администраторскими правами.

Вот такие вот пироги... :) В принципе это все, но только пришла мне тут кое-какая мысль. Что будет, если на /etc/passwd поставить доступ только пользователю root.

dron~# chmod 600 /etc/passwd

Заходим под другим пользователем в систему и пробуем запустить нашу программ (не забудьте ее записать в домашний каталог этого пользователя):

dron~$ ./a.out
Real User ID = 1000

Как видите никакой информации получить мы не смогли. А если запускаем программу вывода переменных среды, то все окей:

dron~# ./a1.out
USER = 'dron'
HOME = '/home/dron'
PATH = '/usr/bin:/usr/local/bin'

Вот, что происходит. Если вдруг "неопытный" администратор системы отключит по соображениям "безопасности" доступ к файлу /etc/passwd, то многие программы недальновидных разработчиков могут просто перестать работать. Что нам остается ?! Ну, я думаю первым делом вы все вызовите команду chmod 644 /etc/passwd. А, во-вторых, перепишите код таким образом, чтобы если что-то не так с функцией getpwuid, то программа пользовалась переменными среды, как единственно доступной в данном случае информацией.

Собственно говоря, на этом все. Вы уже начинаете чувствовать, что не все просто в нашем мире linux ?


Предыдущий Шаг | Следующий Шаг | Оглавление
Автор Кузин Андрей.