Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Первае шаги в программировании Linux

.doc
Скачиваний:
13
Добавлен:
08.04.2015
Размер:
377.34 Кб
Скачать

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

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

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

  • USER - имя пользователя

  • HOME - путь до пользовательской домашней директории

  • PATH - пути для поиска запускаемых программ (разделены двоеточием)

Получить переменные среды можно с помощью функции 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 ?

Шаг 14 - Получение данных из shadow password

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

Для работы со скрытыми паролями надо подключить файл shadow.h и вам станут доступны аналогичные процедуры:

#include <shadow.h>

struct spwd *getspnam (const char *name);

Обратите внимание, что в файле shadow.h нет определения функции получения информации о пользователе по его UID. Т.е. для работы нужно знать имя пользователя, соответственно сначала нужно воспользоваться функцией getpwuid(), чтобы по UID получить имя.

Функция getspnam() возвращает структуру struct spwd, либо NULL в случае неудачи. Данная структура определена следующим образом:

struct spwd

{

char *sp_namp; /* Login name. */

char *sp_pwdp; /* Encrypted password. */

long int sp_lstchg; /* Date of last change. */

long int sp_min; /* Minimum number of days between changes. */

long int sp_max; /* Maximum number of days between changes. */

long int sp_warn; /* Number of days to warn user to change

the password. */

long int sp_inact; /* Number of days the account may be

inactive. */

long int sp_expire; /* Number of days since 1970-01-01 until

account expires. */

unsigned long int sp_flag; /* Reserved. */

};

Как видите структура гораздо больше, чем passwd. Большинство полей отвечает за временные параметры пароля, такие как его минимальное и максимальное время жизни, а также время жизни всего аккаунта.

И наверняка Вы заметили, что я немного соврал Вам в прошлый раз, когда сказал, что /etc/shadow содержит информацию аналогичную /etc/passwd. Получается, что кроме логина и пароля Вы тут не найдете домашней директории и шелла. В принципе верно, зачем хранить одно и тоже в нескольких местах ?! Выходит, что нам не удастся "отбиться от коллектива" и придется пользоваться обоими файлами.

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

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <pwd.h>

#include <shadow.h>

int main(){

struct passwd *userinfo;

struct spwd *passw;

uid_t userid;

userid = getuid();

userinfo = getpwuid(userid);

if (userinfo != NULL){

passw = getspnam(userinfo->pw_name);

if (passw != NULL){

printf("user login: %s\n",userinfo->pw_name);

printf("user home: %s\n",userinfo->pw_dir);

printf("user shell: %s\n",userinfo->pw_shell);

printf("user password: %s\n",userinfo->pw_passwd);

printf("user shadow password: %s\n",passw->sp_pwdp);

printf("user last change: %ld\n",passw->sp_lstchg);

};

};

return 0;

};

Компилируем и запускаем:

dron~# ./gcc shadowtest.c -o shadowtest

dron~# ./shadowtest

user login: root

user home: /root

user shell: /bin/bash

user password: x

user shadow password: $1$02p9xyDo$gnkh4vts/rArhJselceTV1

user last change: 12028

Как видите пароль нам получить удалось только из структуры struct spwd. Но он зашифрованный алгоритмом MD5, в данном случае настоящий пароль 12345678 (можете не мучаться над взломом :). Тут кстати следует поговорить о том, как хранятся пароли. Понятное дело, что если пароли будут храниться в виде plain text, т.е. в виде текста "как есть", то можно будет узнать пароль для любого пользователя совершенно спокойно. Современные правила безопасности вообще не разрешают хранить пароль в таком виде. Вместо этого пароль хранится в виде хеша от настояшего пароля. Функция вырабатывающая хеш берет настоящий пароль и вырабатывает на его основе уникальную последовательность чисел, которую не возможно обратно преобразовать в пароль, потому что математические функции работающие над выработкой пароля специально создаются однонаправленными. Создание таких процедур является сложной криптографической задачей и порой под силу только крупным научно-исследовательским институтам. К примеру у нас в России существуют засекреченные алгоритмы, некоторые из которых разрабатывались в течение 10 лет, так вот представьте каких трудов это стоит и представьте какие эти алгоритмы совершенные. Взять к примеру наш алгоритм шифрования ГОСТ 28147-89, который существует с 89 года и до сих пор остается одним из самых защищенных (он может иметь длину ключа 256 бит, в то время как DES имеет всего 56 бит и при нынешнем развитии компьютеров является чрезвычайно устаревшим). Однако для выработки хеша в системах Linux используются в основном алгоритмы DES и MD5, хотя первый уже используется крайне редко.

Так вот, сравнение правильности пароля происходит следующим образом. Программа получает настоящий пароль от пользователя, потом вырабатывает на его основе хеш и сравнивает его с тем, который она получила из файла /etc/shadow. Если хеши не совпадают, то значит пароли разные.

Шаг 15 - Работа с паролями системы с помощью функции crypt()

Уже многие из Вас задали себе закономерный вопрос "как же работать с паролями", т.е. как получить тот хеш, о котором шла речь раньше. Давайте раберемся с этим.

За генерацию паролей отвечает функция crypt(). Подключить ее к программе можно так:

#define _XOPEN_SOURCE

#include <unistd.h>

char *crypt(const char *key, const char *salt);

Вместо заголовочного файла unistd.h для подключения функций шифрования можно использовать другой файл - crypt.h. В принципе без разницы, они оба имеют одинаковые определения функции crypt().

Для работы функции требуется два параметра:

  • key - это секретный пароль пользователя, который требуется зашифровать

  • salt - это так называемый "открытый ключ", или же в понятиях алгоритмов шифрования инициализационные данные. Данный параметр должен состоять из символов a-zA-Z0-9./, т.е. из любых латинских букв, цифр или некоторых символов.

Если же с паролем все ясно, то salt может заставить задуматься. Давайте посмотрим на что влияет этот параметр. Напишем тестовую программку crypt.c:

#include <stdlib.h>

#include <crypt.h>

int main(){

printf("crypt(\"password\",\"ab\") = \"%s\"\n",crypt("password","ab"));

printf("crypt(\"password\",\"ab12\") = \"%s\"\n",crypt("password","ab12"));

printf("crypt(\"password\",\"ac\") = \"%s\"\n",crypt("password","ac"));

printf("crypt(\"password\",\"ac123\") = \"%s\"\n",crypt("password","ac123"));

printf("crypt(\"password\",\"1a\") = \"%s\"\n",crypt("password","1a"));

printf("crypt(\"password\",\"1a.\") = \"%s\"\n",crypt("password","1a."));

return 0;

};

Данная программа пытается выработать хеш с разным сальтом для пароля "password". Компилируем программу:

dron~# gcc crypt.c -o crypt

/tmp/ccBXde1R.o: In function `main':

/tmp/ccBXde1R.o(.text+0x17): undefined reference to `crypt'

/tmp/ccBXde1R.o(.text+0x3f): undefined reference to `crypt'

/tmp/ccBXde1R.o(.text+0x67): undefined reference to `crypt'

/tmp/ccBXde1R.o(.text+0x8f): undefined reference to `crypt'

/tmp/ccBXde1R.o(.text+0xb7): undefined reference to `crypt'

/tmp/ccBXde1R.o(.text+0xdf): more undefined references to `crypt' follow

collect2: ld returned 1 exit status

Не так быстро. Просто так откомпилировать программу нельзя, надо обязательно подключить библиотеку libcrypt.a, делается это такой командой:

dron~# gcc crypt.c -o crypt -lcrypt

dron~# ./crypt

crypt("password","ab") = "abJnggxhB/yWI"

crypt("password","ab12") = "abJnggxhB/yWI"

crypt("password","ac") = "acBxUIBkuWIZE"

crypt("password","ac123") = "acBxUIBkuWIZE"

crypt("password","1a") = "1abtv8E0hkEd6"

crypt("password","1a.") = "1abtv8E0hkEd6"

Посмотрите на результат работы функции. Хоть я и задавал разные salt некоторые хеши получились совершенно одинаковыми. Из этого можно сделать вывод и том, что в нем играют роль только первые два символа. Значения этих символов используются для шифрования пароля алгоритмом DES. Заметьте также, что salt входит в хеш как его начальная часть. И это правильно, ведь если его удалить, то неизвестно, что надо использовать для проверки достоверности пароля. На самом деле, как мы узнаем дальше, это сделано специально для того, чтобы можно было без особых мучений с паролем из /etc/shadow сразу же отправлять его в функцию crypt().

Хорошо. Нам теперь, думаю, стало понятно как работает алгоритм DES, а как же MD5 ? Ведь, как я уже говорил, пароли сегодня чаще всего хранятся в виде MD5. Хороший вопрос. Ответ на него я искал некоторое время. Исходя из того, что совершенно точно функция crypt() должна работать с MD5 я начал рыскать в исходнике gnu-pop3d, который совсем недавно чуть ли не переписал заново :) Пролистав все и не обнаружив ни #define ни хитрых #include я попробовал залезть в файл библиотеки /usr/lib/libcrypt.a. Получить список функций из нее можно командой nm:

dron~# nm /usr/lib/libcrypt.a

crypt-entry.o:

0000000000000117 t Letext

0000000000000000 T __crypt_r

U __md5_crypt

U __md5_crypt_r

U _ufc_dofinalperm_r

U _ufc_doit_r

U _ufc_foobar

U _ufc_mk_keytab_r

U _ufc_output_conversion_r

U _ufc_setup_salt_r

00000000000000d0 T crypt

0000000000000000 W crypt_r

00000000000000d0 W fcrypt

0000000000000000 r md5_salt_prefix

U strncmp

U strncpy

md5-crypt.o:

000000000000070a t Letext

U __assert_fail

U __errno_location

0000000000000690 T __md5_crypt

0000000000000000 T __md5_crypt_r

U __md5_finish_ctx

U __md5_init_ctx

...............

Посмотрите, список функций очень большой, поэтому не привожу весь, но видно определенно, что libcrypt.a содержит функции для работы с MD5. Но тогда как ?! Ведь нет никаких параметров дополнительных. А все оказалось куда проще %) Посмотрите на листинг, видите имя md5_salt_prefix. Не правда ли очень говорящее название ?! А теперь посмотрите типичный пароль закодированный с помощью MD5:

$1$/DrNy/Cv$ZBydbOBsEvdI5u5sib2X/0

$1$02p9xyDo$gnkh4vts/rArhJselceTV1

Не видите ничего странного ?! Правильно, у них структура отличается от паролей на DES и выглядит следующим образом:

$1$..salt..$.........hash.........

Именно по этой структуре функция crypt() определяет каким методом ей шифровать пароль. Не поленимся однако и посмотрим исходники crypt() в библиотеке libc. Вот к примеру строки из файла crypt-entry.c:

/* Define our magic string to mark salt for MD5 encryption

replacement. This is meant to be the same as for other MD5 based

encryption implementations. */

static const char md5_salt_prefix[] = "$1$";

.......

/* Try to find out whether we have to use MD5 encryption replacement.*/

if (strncmp (md5_salt_prefix, salt, sizeof (md5_salt_prefix) - 1) == 0)

return __md5_crypt_r (key, salt, (char *) data, sizeof (struct crypt_data));

Помоему классно :) Именно "магическая строчка" $1$ и является тем методом переключения между различными алгоритмами. Тут еще интересен вопрос о том, какой длины должен быть этот salt, изучая исходниках дальше Вы сможете найти в файле md5-crypt.c строчки:

/* Find beginning of salt string. The prefix should normally always

be present. Just in case it is not. */

if (strncmp (md5_salt_prefix, salt, sizeof (md5_salt_prefix) - 1) == 0)

/* Skip salt prefix. */

salt += sizeof (md5_salt_prefix) - 1;

salt_len = MIN (strcspn (salt, "$"), 8);

key_len = strlen (key);

Тут не вооруженным глазом видно, что после $1$ ищется второй символ $ и берется длина строки ограниченная этими признаками. Далее выбирается минимум между длиной строки и 8, т.е. получается что salt в алгоритме MD5 может быть любой длины не больше 8-ми. Это и требовалось доказать, теперь давайте попробуем :)

#include <stdlib.h>

#include <crypt.h>

int main(){

printf("crypt(\"12345678\",\"$1$abasdlkasl123$\") = \"%s\"\n",

crypt("password","$1$abasdlkasl123$"));

printf("crypt(\"12345678\",\"$1$dfg$\") = \"%s\"\n",

crypt("password","$1$dfg$"));

return 0;

};

Снова компилируем, и не забываем про библиотеку crypt:

dron~# gcc crypt1.c -o crypt1 -lcrypt

dron~# ./crypt1

crypt("12345678","$1$abasdlkasl123$") = "$1$abasdlka$z9aVWR2l14E3WngLCABSt1"

crypt("12345678","$1$dfg$") = "$1$dfg$fF0Vo9cC5CyBY827ltEdn0"

Все получилось :) А Вы как думали ?! И обратите внимание на то, что длинный salt в первом случае обрезался до 8-ми символов. Кстати, помоему длина 8 символов куда лучше, чем два. Это еще раз говорит о том, что метод MD5 лучше DES. И раз вообще заговорили про размер, то сравните длину получающихся хешей от работы этих алгоритмов.

Теперь, собственно говоря, сам процесс проверки пароля. Как Вы уже наверно поняли он сводится к простому сравнению, смотрим код:

#include <stdlib.h>

#include <unistd.h>

#include <sys/types.h>

#include <pwd.h>

#include <shadow.h>

int main(int argc,int *argv){

struct passwd *userinfo;

struct spwd *passw;

uid_t userid;

if (argc<2) {

printf("Try to use: %s uin password\n",argv[0]);

return 1;

};

userid = (uid_t)atoi(argv[1]);

userinfo = getpwuid(userid);

if (userinfo != NULL){

passw = getspnam(userinfo->pw_name);

if (passw != NULL){

printf("Try to test password for \"%s\": ",userinfo->pw_name);

if (strcmp(passw->sp_pwdp,crypt(argv[2],passw->sp_pwdp))==0)

printf ("Ok...\n");

else

printf ("Failed...\n");

} else

printf("Can't find password for user with UIN = %s\n",argv[1]);

} else

printf("Can't find user with UIN = %s\n",argv[1]);

return 0;

};

Теперь компилируем и запускаем:

dron~# ./testpasswd

Try to use: ./testpasswd uin password

dron~# ./testpasswd 1000 12345678

Try to test password for "dron": Ok...

dron~# ./testpasswd 1000 1234

Try to test password for "dron": Failed...

Помоему мы научились проверять правильность паролей для пользователей :) Только не забывайте про то, что пароли из /etc/shadow доступны только из под root, но об этом мы не однократно говорили раньше.

И еще я все время пытаюсь Вам привить то, что исходники не только для того, чтобы их компилировать. Они нужны для того, чтобы изучать программирование и мы будем продолжать их просматривать :) Мало ли может глюки найдем %)

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

Мы с Вами раньше узнали как получать информацию о пользователях, но не секрет, что эти пользователи могут быть объединены в группы. Для разграничения доступа в системе используются оба идентификатора: идентификатор пользователя UID и идентификатор группы GID. С помощью совокупности этих идентификаторов можно достаточно гибко настраивать безопасность системы.

Для работы с группами требуется подключить файл grp.h и Вам станут доступны следующие функции:

#include <grp.h>

#include <sys/types.h>

struct group *getgrnam(const char *name);

struct group *getgrgid(gid_t gid);

Функция getgrnam() ищет группу в файле /etc/group с именем name и возвращает указатель на структуру struct group, либо NULL в случае неудачи.

Функция getgrgid() ищет группу по идентификатору и также возвращает стуктуру struct group, либо NULL в случае неудачи. Данная структура имеет следующий вид:

struct group {

char *gr_name; /* имя группы */

char *gr_passwd; /* групповой пароль */

gid_t gr_gid; /* идентификатор группы */

char **gr_mem; /* члены группы */

};

Назначение полей думаю не требуют объяснения. Один лишь момент в том, что поле members это массив указателей на строки, в котором для обозначения конца последний элемент равен NULL.

Давайте попробуем написать программку, которая будет выводить информацию о группе:

#include <stdlib.h>

#include <grp.h>

#include <sys/types.h>

int main(){

struct group *g= NULL;

char **p = NULL;

g = getgrnam("webmasters");

if (g != NULL){

printf ("gr_name = \"%s\"\n",g->gr_name);

printf ("gr_passwd = \"%s\"\n",g->gr_passwd);

printf ("gr_gid = \"%d\"\n",g->gr_gid);

printf ("gr_members = ");

p = g->gr_mem;

while (*p != NULL){

printf ("\"%s\" ", *p);

p++;

};

printf ("\n");

};

return 0;

};

После компиляции и запуска программы мы получим список пользователей системы относящихся к группе webmasters:

dron~# ./a.out

gr_name = "webmasters"

gr_passwd = "x"

gr_gid = "102"

gr_members = "dron" "kost" "aneta"

Теперь, если посмотреть содержимое файла /etc/group можно увидеть следующую строку:

webmasters:x:102:dron,kost,aneta

Важно отметить то, что в файле /etc/group совершенно не обязательно должны быть перечислены все пользователи из этой группы. В описании каждой группы добавляются только те пользователи, которые входят в несколько групп пользователей одновременно. Все это задается на этапе создания нового пользователя. В первую очередь ему задается основная группа, к которой он принадлежит, как правило это группа users. Далее, если требуется, ему назначаются дополнительные группы, в которые он может входить. После этого в описание этих дополнительных групп добавляется имя этого нового пользователя.

Шаг 17 - Работа с переменными окружения - setenv и getenv

Раньше мы познакомились с тем, как получать параметры в программе с помощью семейства функций getopt (подробнее читайте "Шаг 10 - Передача опций в программу - getopt" и "Шаг 11 - Передача длинных опций в программу - getopt_long"). Но существует еще один метод - переменные окружения или переменные среды. С ними мы успели уже познакомиться раньше в шаге "Шаг 13 - Получение информации о пользователе". С помощью данных переменных можно передавать в программу дополнительные специфические параметры, которые могут использоваться несколькими программами одновременно и они характеризуют "среду", в которой выполняется программа.

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

#!/bin/bash

/yoyosystem/cheduler --time=60 --work-dir=/yoyosystem/data --temp-dir=/tmp/yoyosystem

/yoyosystem/counter -c 25 -w 31 --work-dir=/yoyosystem/data --temp-dir=/tmp/yoyosystem

/yoyosystem/pusher --stack-size=512 -m 12 --work-dir=/yoyosystem/data --temp-dir=/tmp/yoyosystem

Ну, как ?! Громодко ! А вот как более красиво:

#!/bin/bash

export YOYO_WORK_DIR="/yoyosystem/data"

export YOYO_TEMP_DIR="/tmp/yoyosystem"

/yoyosystem/cheduler --time=60

/yoyosystem/counter -c 25 -w 31

/yoyosystem/pusher --stack-size=512 -m 12

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

А теперь собственно давайте познакомимся с этими переменными среды. Библиотека glibc предоставляет весь спектр функций для работы с ними в заголовочном файле stdlib.h.

Сначала познакомимся с самим массивом переменных окружения, который определен в заголовочном файле unistd.h в таком виде:

/* NULL-terminated array of "NAME=VALUE" environment variables. */

extern char **__environ;

#ifdef __USE_GNU

extern char **environ;

#endif

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

#include <stdlib.h>

#include <unistd.h>

int main(int argc,char **argv) {

int i=0;

while (__environ[i] != NULL) {

printf("%s\n",__environ[i]);

i++;

};

return 0;

};

Данная программа выведет весь список переменных среды окружения, которые доступны программе:

root@darkstar:/@@@@@@# ./a.out