Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
SP_Intro0(1).pdf
Скачиваний:
18
Добавлен:
11.02.2016
Размер:
487.54 Кб
Скачать

Будьте бдительны и Ваши программы будут работать без ошибок !!!

Шаг 12 - Вывод сообщений об ошибках программы

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

Вывод сообщений обычно делается через стандартный поток stderr, который перенаправляет все данные на консоль. Можно делать это и через stdout, но он может быть перенаправлен в файл или куда-либо еще, к тому же он обладает буфером, который ему не позволяет выводить данные моментально и если программа резко "обрушится", то Вы можете не увидеть нужных сообщений вообще. В потоке stderr буфер отключен, поэтому вызов функции fflush(stderr) не обязателен.

Все это конечно хорошо, если Вы разрабытаваете пользовательское консольное приложение, но как быть, если Ваша программа является сетевым приложением, например POP3 или каким-нибудь другим сервером. В этом случае работа с stderr не возможна, и отладка мягко выражаясь может сильно усложниться. Но не все так печально. Для этих случаев существует демон сообщений syslogd, который все сообщения от ядра и программ записывает в файлы хранящиеся в папке /var/log.

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

#include <syslog.h>

void openlog( char *ident, int option, int facility) void syslog( int priority, char *format, ...)

void closelog( void )

Предназначение функции closelog() думаю ясно, она закрывает дескриптор, который использовался для передачи сообщений в system logger. Ее использование опционально и, если вы забудете ее вызвать, то ничего катастрофичного не произойдет.

Для начала вывода сообщений Вам придется передать в функцию openlog() несколько необходимых параметров:

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

option - установки открываемого соединения, которые посредством операции OR могут складываться из

следующих:

o

LOG_CONS - вывод напрямую в системную консоль, если вдруг происходит ошибка во время

o

отправления сообщения

LOG_NDELAY - открывать соединение сразу, обычно соединение открывается после появления

o

первого сообщения

LOG_PERROR - выводить сообщения в stderr

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

facility - позволяет задать тип программы, которая выводит сообщение. Это полезно для того, чтобы разделять сообщения от различных программ и записывать их в разные файлы. Все это настраивается для syslogd файлом конфигурации /etc/syslog.conf. А значения этого параметра могут быть следующими:

oLOG_AUTH - сообщения безопасности/авторизации (рекомендуется использовать LOG_AUTHPRIV)

o

LOG_AUTHPRIV - приватные сообщения безопасности/авторизации

o

LOG_CRON - сообщения от демонов времени (например, cron или at)

o

LOG_DAEMON - сообщения от других демонов системы

o

LOG_KERN - сообщения ядра системы

o

LOG_LOCAL0...LOG_LOCAL7 - зарезервированы для локального использования

o

LOG_LPR - подсистема принтера

o

LOG_MAIL - почтовая подсистема

o

LOG_NEWS - подсистема новостей USENET

o

LOG_SYSLOG - внутренние сообщения сгенерированные syslogd

o

LOG_USER (по умолчанию) - сообщения пользовательского уровня

o

LOG_UUCP - сообщения системы UUCP

Вызов функции openlog() также не обязателен, она будет автоматически вызвана при необходимости во время использования syslog(), но идентификатор программы будет установлен в NULL, что я думаю не будет считаться хорошим тоном.

Ну, и чтобы выводить сами сообщения надо использовать функцию syslog(), работа с которой похожа на работу с функцией printf, за исключением того, что сообщению можно задать приоритет(или тип), т.е. его важность. Задается приоритет параметром priority, который может иметь следующие значения:

LOG_EMERG - система не работает, грубо говоря в обмороке и требует госпитализации :)

LOG_ALERT - необходимо немедленно принять меры

LOG_CRIT - критическое состояние

LOG_ERR - ошибочное состояние

LOG_WARNING - состояние предупреждения

LOG_NOTICE - нормальное, но значимое, состояние

LOG_INFO - информационное сообщение

LOG_DEBUG - сообщение отладки, то что как раз нужно при разработке

А теперь попробуем написать программу test.c, использующую syslog:

#include <stdlib.h> #include <stdio.h> #include <syslog.h>

#define DEBUG

int main(){

int i=0; openlog("test",LOG_PID,LOG_USER);

#ifdef DEBUG

syslog(LOG_DEBUG,"try to sending 10 messages"); #endif

for (i=0;i<10;i++){

syslog(LOG_INFO,"info message [i = %d] ",i);

};

#ifdef DEBUG

syslog(LOG_DEBUG,"try log to stderr"); #endif

closelog();

openlog("test_stderr",LOG_PERROR | LOG_PID,LOG_USER); syslog(LOG_INFO,"this is attempt to use stderr for syslog"); closelog();

return 0;

};

Компилируем программу и попробуем запустить: dron~# ./test

test_stderr[6222]: this is attempt to use stderr for syslog

Теперь можем зайти в файл /var/log/messages и посмотреть, что там получилось. А получилось вот что:

Dec 20 11:25:04 dron-linux test[6222]: info message [i = 0] Dec 20 11:25:04 dron-linux test[6222]: info message [i = 1] Dec 20 11:25:04 dron-linux test[6222]: info message [i = 2] Dec 20 11:25:04 dron-linux test[6222]: info message [i = 3]

Dec 20 11:25:04 dron-linux test[6222]: info message [i = 4] Dec 20 11:25:04 dron-linux test[6222]: info message [i = 5] Dec 20 11:25:04 dron-linux test[6222]: info message [i = 6] Dec 20 11:25:04 dron-linux test[6222]: info message [i = 7] Dec 20 11:25:04 dron-linux test[6222]: info message [i = 8] Dec 20 11:25:04 dron-linux test[6222]: info message [i = 9]

Dec 20 11:25:04 dron-linux test_stderr[6222]: this is attempt to use stderr for syslog

Помоему классно, но почему-то не хватает некоторых сообщений. Посмотрим /var/log/debug и увидим, что все на месте :)

Dec 20 11:25:04 dron-linux test[6222]: try to sending 10 messages Dec 20 11:25:04 dron-linux test[6222]: try log to stderr

То есть тут мы можем увидеть, что сообщения разных типов выводятся в разные файлы. Настроить это можно с помощью файла /etc/syslog.conf. К примеру в данном случае он выглядит вот так:

#/etc/syslog.conf

#For info about the format of this file, see "man syslog.conf" *.=info;*.=notice /var/log/messages

*.=debug

/var/log/debug

*.err

/var/log/syslog

Обратите также внимание, что сообщения, которые должны были выводиться в stderr дублируются в файл. Т.е. по большому счету, даже если поток stderr будет не доступен, то сообщения программы все равно дойдут до вас и это очень хорошо.

Шаг 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, то многие программы недальновидных разработчиков могут просто перестать

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]