Главная · Поиск книг · Поступления книг · Top 40 · Форумы · Ссылки · Читатели

Настройка текста
Перенос строк


    Прохождения игр    
Demon's Souls |#13| Storm King
Demon's Souls |#11| Мaneater part 2
Demon's Souls |#10| Мaneater (part 1)
Demon's Souls |#9| Heart of surprises

Другие игры...


liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня
Rambler's Top100
Образование - Богатырев А. Весь текст 1009.15 Kb

Хрестоматия по программированию на Си в Unix

Предыдущая страница Следующая страница
1 ... 39 40 41 42 43 44 45  46 47 48 49 50 51 52 ... 87
    #define FOUND    1 /* ответ найден    */
    #define NOTFOUND 0 /* ответ не найден */
    int value;         /* результат */
    main(){    int i;
      for(i=2; i < 10; i++){
          printf( "пробуем i=%d\n", i);
          if( test1(i) == FOUND ){
              printf("ответ %d\n", value); break;
          }
      }
    }
    test1(i){  int j;
      for(j=1; j < 10 ; j++ ){
          printf( "пробуем j=%d\n", j);
          if( test2(i,j) == FOUND ) return FOUND;
          /* "сквозной" return */
      }
      return NOTFOUND;
    }
    test2(i, j){
      printf( "пробуем(%d,%d)\n", i, j);
      if( i * j == 21 ){
          printf( "  Годятся (%d,%d)\n", i,j);
          value = j; return FOUND;
      }
      return NOTFOUND;
    }

Вот ответ, использующий нелокальный переход вместо цепочки return-ов:

    #include 
    jmp_buf jmp;
    main(){   int i;
      if( i = setjmp(jmp))  /* после прыжка */
            printf("Ответ %d\n", --i);
      else  /* установка точки */
        for(i=2; i < 10; i++)
          printf( "пробуем i=%d\n", i), test1(i);
    }
    test1(i){ int j;
      for(j=1; j < 10 ; j++ )
          printf( "пробуем j=%d\n", j), test2(i,j);
    }
    test2(i, j){
      printf( "пробуем(%d,%d)\n", i, j);
      if( i * j == 21 ){
         printf( "  Годятся (%d,%d)\n", i,j);
         longjmp(jmp, j + 1);
      }
    }

Обратите внимание, что при возврате ответа через второй аргумент longjmp мы прибавили
1,  а  при  печати  ответа  мы эту единицу отняли. Это сделано на случай ответа j==0,
чтобы функция setjmp не вернула бы в этом случае значение 0 (признак установки  конт-
рольной точки).

6.7.2.  В чем ошибка?

    #include 

А. Богатырев, 1992-95                  - 235 -                              Си в UNIX

    jmp_buf jmp;
    main(){
         g();
         longjmp(jmp,1);
    }
    g(){ printf("Вызвана g\n");
         f();
         printf("Выхожу из g\n");
    }
    f(){
         static n;
         printf( "Вызвана f\n");
         setjmp(jmp);
         printf( "Выхожу из f %d-ый раз\n", ++n);
    }

Ответ: longjmp делает прыжок в функцию f(), из которой уже произошел возврат управле-
ния. При переходе в тело функции в обход ее заголовка не выполняются машинные команды
"пролога" функции - функция остается "неактивированной". При  возврате  из  вызванной
таким  "нелегальным"  путем  функции  возникает ошибка, и программа падает. Мораль: в
функцию, которая НИКЕМ НЕ ВЫЗВАНА, нельзя передавать управление.  Обратный  прыжок  -
из  f()  в main() - был бы законен, поскольку функция main() является активной, когда
управление находится в теле функции f().  Т.е. можно "прыгать" из вызванной функции в
вызывающую: из f() в main() или в g(); и из g() в main();

    --        --
     |   f    |  стек      прыгать
     |   g    |  вызовов   сверху вниз
     |   main |  функций   можно - это соответствует
     ----------            выкидыванию нескольких
                           верхних слоев стека

но нельзя наоборот: из main() в g() или f(); а также  из  g()  в  f().   Можно  также
совершать прыжок в пределах одной и той же функции:

    f(){ ...
            A:   setjmp(jmp);
                 ...
                 longjmp(jmp, ...); ...
                 /* это как бы goto A; */
    }

6.8.  Хозяин файла, процесса, и проверка привелегий.
     UNIX - многопользовательская система. Это значит,  что  одновременно  на  разных
терминалах, подключенных к машине, могут работать разные пользователи (а может и один
на нескольких терминалах). На каждом терминале работает  свой  интерпретатор  команд,
являющийся потомком процесса /etc/init.

6.8.1.  Теперь - про функции, позволяющие узнать некоторые данные про любого  пользо-
вателя  системы.   Каждый  пользователь  в UNIX имеет уникальный номер: идентификатор
пользователя (user id), а также уникальное имя: регистрационное имя, которое он наби-
рает  для  входа  в  систему.   Вся  информация  о  пользователях  хранится  в  файле
/etc/passwd. Существуют функции, позволяющие по номеру пользователя узнать  регистра-
ционное имя и наоборот, а заодно получить еще некоторую информацию из passwd:

А. Богатырев, 1992-95                  - 236 -                              Си в UNIX

    #include 
    #include 
    struct passwd *p;
    int   uid;   /* номер */
    char *uname; /* рег. имя */

    uid = getuid();
    p   = getpwuid( uid   );
            ...
    p   = getpwnam( uname );

Эти функции возвращают указатели на статические структуры, скрытые внутри этих  функ-
ций.  Структуры эти имеют поля:

    p->pw_uid     идентиф. пользователя (int uid);
    p->pw_gid     идентиф. группы пользователя;

            и ряд полей типа char[]
    p->pw_name    регистрационное имя пользователя (uname);
    p->pw_dir     полное имя домашнего каталога
      (каталога, становящегося текущим при входе в систему);
    p->pw_shell   интерпретатор команд
      (если "", то имеется в виду /bin/sh);
    p->pw_comment произвольная учетная информация (не используется);
    p->pw_gecos   произвольная учетная информация (обычно ФИО);
    p->pw_passwd  зашифрованный пароль для входа в
       систему. Истинный пароль нигде не хранится вовсе!

Функции возвращают значение p==NULL, если указанный пользователь не существует  (нап-
ример,  если  задан неверный uid).  uid хозяина данного процесса можно узнать вызовом
getuid, а uid владельца файла - из поля st_uid структуры, заполняемой системным вызо-
вом stat (а идентификатор группы владельца - из поля st_gid).  Задание: модифицируйте
наш аналог программы ls, чтобы он выдавал в  текстовом  виде  имя  владельца  каждого
файла в каталоге.

6.8.2.  Владелец файла может изменить своему файлу идентификаторы владельца и  группы
вызовом

    chown(char *имяФайла, int uid, int gid);

т.е. "подарить" файл другому пользователю.  Забрать чужой файл себе невозможно.   При
этой операции биты S_ISUID и S_ISGID в кодах доступа к файлу (см. ниже) сбрасываются,
поэтому создать "Троянского коня" и, сделав его хозяином суперпользователя,  получить
неограниченные привелегии - не удастся!

6.8.3.  Каждый файл имеет своего владельца (поле di_uid в I-узле на  диске  или  поле
i_uid в копии I-узла в памяти ядра[*]).  Каждый процесс также  имеет  своего  владельца
(поля u_uid и u_ruid в u-area).  Как мы видим, процесс имеет два параметра, обознача-
ющие владельца. Поле ruid называется "реальным идентификатором" пользователя, а uid -
"эффективным  идентификатором".   При вызове exec() заменяется программа, выполняемая
данным процессом:

____________________
   [*] При открытии файла и вообще при любой операции с файлом, в таблицах  ядра  заво-
дится  копия  I-узла  (для ускорения доступа, чтобы постоянно не обращаться к диску).
Если I-узел в памяти будет изменен, то при закрытии файла (а также периодически через
некоторые  промежутки  времени)  эта копия будет записана обратно на диск.  Структура
I-узла в памяти - struct inode - описана в файле , а на диске  -  struct
dinode - в файле .

А. Богатырев, 1992-95                  - 237 -                              Си в UNIX

    старая программа  exec    новая программа
     ruid -->----------------->---> ruid

     uid  -->--------*-------->---> uid (new)

                        |
                   выполняемый файл
                    i_uid (st_uid)

Как видно из этой схемы, реальный идентификатор хозяина процесса наследуется.  Эффек-
тивный  идентификатор  обычно также наследуется, за исключением одного случая: если в
кодах доступа файла (i_mode) выставлен бит S_ISUID (set-uid bit),  то  значение  поля
u_uid в новом процессе станет равно значению i_uid файла с программой:

    /* ... во время exec ... */
    p_suid = u_uid;     /* спасти */
    if( i_mode & S_ISUID ) u_uid = i_uid;
    if( i_mode & S_ISGID ) u_gid = i_gid;

т.е. эффективным владельцем процесса станет владелец файла.  Здесь gid - это  иденти-
фикаторы  группы  владельца  (которые тоже есть и у файла и у процесса, причем у про-
цесса - реальный и эффективный).
     Зачем все это надо? Во-первых затем, что ПРАВА процесса на доступ к  какому-либо
файлу  проверяются  именно  для эффективного владельца процесса.  Т.е. например, если
файл имеет коды доступа

    mode = i_mode & 0777;
                  /* rwx rwx rwx */

и владельца i_uid, то процесс, пытающийся открыть этот файл, будет "проэкзаменован" в
таком порядке:

    if( u_uid == 0 )  /* super user */
         то доступ разрешен;
    else if( u_uid == i_uid )
         проверить коды (mode & 0700);
    else if( u_gid == i_gid )
         проверить коды (mode & 0070);
    else проверить коды (mode & 0007);

Процесс может узнать свои параметры:

    unsigned short uid  = geteuid();  /* u_uid  */
    unsigned short ruid = getuid();   /* u_ruid */
    unsigned short gid  = getegid();  /* u_gid  */
    unsigned short rgid = getuid();   /* u_rgid */

а также установить их:

    setuid(newuid);  setgid(newgid);

Рассмотрим вызов setuid. Он работает так (u_uid -  относится  к  процессу,  издавшему
этот вызов):

    if(      u_uid == 0 /* superuser */ )
             u_uid = u_ruid =    p_suid =  newuid;
    else if( u_ruid == newuid || p_suid == newuid )
             u_uid = newuid;
    else     неудача;

Поле p_suid позволяет  set-uid-ной  программе  восстановить  эффективного  владельца,
который был у нее до exec-а.

А. Богатырев, 1992-95                  - 238 -                              Си в UNIX

     Во-вторых, все это надо для следующего случая: пусть у меня есть некоторый  файл
BASE  с  хранящимися  в нем секретными сведениями. Я являюсь владельцем этого файла и
устанавливаю ему коды доступа 0600 (чтение и запись разрешены  только  мне).  Тем  не
менее,  я  хочу  дать другим пользователям возможность работать с этим файлом, однако
контролируя их деятельность.  Для этого я пишу программу, которая выполняет некоторые
действия  с  файлом  BASE,  при этом проверяя законность этих действий, т.е. позволяя
делать не все что попало, а лишь то, что я в ней предусмотрел, и под жестким  контро-
лем.   Владельцем  файла PROG, в котором хранится эта программа, также являюсь я, и я
задаю этому файлу коды доступа 0711 (rwx--x--x) - всем можно выполнять эту программу.
Все  ли  я  сделал, чтобы позволить другим пользоваться базой BASE через программу (и
только нее) PROG?  Нет!
     Если кто-то другой запустит программу PROG, то  эффективный  идентификатор  про-
цесса  будет  равен  идентификатору этого другого пользователя, и программа не сможет
открыть мой файл BASE.  Чтобы все работало, процесс, выполняющий программу PROG, дол-
жен работать как бы от моего имени. Для этого я должен вызовом chmod либо командой
     chmod u+s PROG
добавить к кодам доступа файла PROG бит S_ISUID.
     После этого, при запуске программы PROG, она будет получать эффективный  иденти-
фикатор,  равный  моему  идентификатору,  и таким образом сможет открыть и работать с
файлом BASE.  Вызов getuid позволяет выяснить, кто вызвал мою  программу  (и  занести
это в протокол, если надо).
     Программы такого типа - не редкость в UNIX, если владельцем программы (файла  ее
содержащего)  является  суперпользователь. В таком случае программа, имеющая бит дос-
тупа S_ISUID работает от имени суперпользователя и может выполнять  некоторые  дейст-
вия, запрещенные обычным пользователям. При этом программа внутри себя делает всячес-
кие проверки и периодически спрашивает пароли, то есть при работе защищает систему от
дураков  и преднамеренных вредителей.  Простейшим примером служит команда ps, которая
считывает таблицу процессов из памяти ядра и распечатывает ее.  Доступ  к  физической
памяти  машины  производится  через файл-псевдоустройство /dev/mem, а к памяти ядра -
/dev/kmem.  Чтение и запись в них позволены только суперпользователю,  поэтому  прог-
Предыдущая страница Следующая страница
1 ... 39 40 41 42 43 44 45  46 47 48 49 50 51 52 ... 87
Ваша оценка:
Комментарий:
  Подпись:
(Чтобы комментарии всегда подписывались Вашим именем, можете зарегистрироваться в Клубе читателей)
  Сайт:
 

Реклама