시스템에서 로그인하고 있는 모든 사용자는 사용자 ID(user ID)라고 불리는 한 개의 단일한 숫자에 의해서 구분된다. 각 프로세스는 그 프로세스를 가진 사용자의 억세스 권한을 알리는 유효 사용자 ID를 갖는다. 사용자들은 억세스 제어를 목적으로 하여 그룹으로 분류된다. 각 프로세스는 그 프로세스가 파일들을 억세스하기 위해서 사용할 수 있는 어떤 그룹들이 있는지를 알리는 한 개 이상의 그룹 ID(group ID)가 있다. 프로세스의 유효 사용자 ID 와 유효 그룹 ID들은 persona의 집합적인 형식이다. 이것은 그 프로세스가 어떤 파일을 억세스 할 수 있는지를 결정한다. 일반적으로, 프로세스는 부모 프로세스로 부터 persona를 상속받지만, 특별한 상황하에서는 그 persona를 변경해서 억세스 권한을 변경할 수 있다.
시스템에 있는 각 파일은 또한 사용자 ID 와 그룹 ID를 갖는다. 억세스제어(access control)는, 실행되고 있는 프로세스의 사용자 ID 와 그룹 ID를 파일의 사용자 ID 와 그룹 ID를 비교함으로써 수행된다. 그 시스템은 등록된 사용자들을 모두 데이터베이스에 저장하고, 다른 데이터베이스에 정의된 모든 그룹들을 저장한다. 그 데이터베이스를 시험하기 위해 사용할 수 있는 라이브러리 함수들이 있다.
컴퓨터 시스템에 등록된 각 사용자들은 사용자 이름(user name) 과 사용자ID(user ID) 에 의해 구분된다. 일반적으로, 각 사용자 이름은 한 개의 단일한 사용자 ID를 갖지만, 같은 사용자 ID에 여러개의 로그인 이름들을 가질 수 있다. 사용자 이름과 그에 해당하는 사용자 ID는 25. 12절 [User Database] 에 설명된, 당신이 억세스 할 수 있는 데이터베이스에 저장되어 있다.
사용자는 그룹 안에 분류된다. 각 사용자 이름은 디폴트로는 한 개의 그룹이지만, 한 개 이상의 그룹에 소속된다. 같은 그룹의 멤버인 사용자들은 그 그룹의 멤버가 아닌 사용자가 억세스할 수 없는 자원들(파일과 같은)을 공유한다. 각 그룹은 한 개의 그룹 이름과 그룹 ID를 갖는다. 25. 13절[Group Database] 에, 그룹 ID 또는 그룹 이름에 대한 정보를 어떻게 찾는지 나와있다.
어느 시간에, 각 프로세스는 프로세스의 권한을 결정하는 한 개가 단일한 사용자 ID와 한 개의 그룹 ID를 갖는다. 그들은 프로세스의 집합적인 persona라고 불리는데, 왜냐하면 그들은 억세스 제어의 목적을 위하여 "그것은 누구이다"를 결정하기 때문이다. 그 ID들은 프로세스의 유효 사용자 ID(effective user ID) 와 유효 그룹 ID(effective group ID) 라고 불린다. 당신의 로그인 쉘은 당신의 사용자 ID 와 당신의 디폴트 그룹 ID로 구성되는 persona로 시작한다. 일반적인 환경에서, 모든 당신의 다른 프로세스들은 그들의 값들을 상속받는다.
프로세스는 그 프로세스를 만들었던 사용자를 확인할 수 있는 실제사용자 ID(real user ID)와 사용자의 디폴트 그룹을 확인하는 실제 그룹 ID(real group ID)를 갖는다. 그들의 값들은 억세스 제어의 역할을 하지 않기 때문에, 우리는 그들을 persona의 부분으로 간주하지 않는다. 하지만 그들은 중요하다. 실제 와 유효 사용자 ID 둘은 모두 프로세스의 생존기간 동안에 변경될 수 있다. 25. 3절 [Why Change Persona] 참조. 부가적으로, 사용자는 여러 개의 그룹에 포함될 수 있기 때문에, persona에는 억세스 권한에 기여하는 부가적인 그룹 ID들이 포함된다. 어떻게 프로세스의 유효 사용자 ID와 그룹 ID들이 파일을 접근하는 권한에 영향을 미치는지에 대한 상세한 정보들은, 9. 8. 6절 [Access Permission] 참조.
프로세스의 사용자 ID는 kill 함수를 사용해서 시그널들을 보낼 수 있는 권한 또한 제어한다. 21. 6. 2절 [Signaling Another Process] 참조.
프로세스를 위하여 사용자 그리고/또는 그룹 ID들을 변경할 필요가 있는 가장 명백한 상황이 있는 곳은 로그인 프로그램이다. 로그인이 실행을 시작할 때, 그 사용자 ID는 루트이다. 작업은 로그인하고 있는 사용자의 사용자 ID와 그룹 ID들을 가진 쉘을 시작하는 것이다. (이것을 완전히 수행하기 위해서, 로그인은 persona 뿐만 아니라 실제 사용자와 그룹 ID들을 설정해야만 한다. 하지만 이것은 특별한 경우이다. ) persona를 변경하는 더 일반적인 경우는 보통의 사용자 프로그램에서 실제로 그 프로그램을 실행시키고 있는사용자에게는 억세스가 가능하지 않는 자원을 사용자가 억세스할 필요가 있을 때이다.
예를 들어, 당신이 당신의 프로그램에 의해 제어되지만 다른 사용자에 의해서 직접적으로 읽거나 갱신될 수 없는 어떤 파일이 있다고 했을 때, 다른 사용자에게는 개방되지 않은 어떤 종류의 프로토콜의 기능을 원하거나, 또는 그것이 가지고 있는 정보의 비밀이나 완전을 보장하기 원할 때, persona를 변경하는 것이 필요하다. 이 제한된 억세스의 종류는 원하는 자원과 매치되도록 유효 사용자와 그룹 ID를 프로그램이 변경함으로써 가능하다.
그러면, 파일안에 점수를 저장하는 게임 프로그램을 상상해보자. 게임프로그램 자체는 누가 그것을 실행시키는지에 상관없이 이 파일을 갱신할 수 있도록 할 필요가 있지만, 만일 사용자가 그 게임을 실행시키지 않고도 그 점수파일에 기록이 가능하다면, 그들은 그들이 좋아하는 어느 점수로 그 점수파일에 그들 점수를 기록할 수 있다. 어떤 사람은 이것을 상상할 수도 없거나, 아니면 비난할 것이다. 이러한 것은 점수 파일을 소유하는 새로운 사용자 ID와 로그인 이름을 만들고 이 사용자에 의해서만 오직 기록 가능하도록 그 파일을 만들면 방지할 수 있다. 그러면, 게임 프로그램이 이 파일을 갱신하기 원할 때, 그것은 그 게임의 유효 사용자 ID를 변경할 수 있다. 실제로, 프로그램은 점수파일에 기록할 수 있는 게임의 persona를 받아 들여야만 한다.
프로세스의 persoan을 변경하기 위한 능력은 무심한 비밀의 침해나, 또는 고의적인 남용의 소스(source)가 될 수 있다. persona를 변경하는, 프로그램에 포함된 문제는 특별한 상황들에 제한이 있기 때문이다.
당신은 제멋대로 당신이 원한다고 당신의 사용자 ID 나 그룹 ID를 변경 할 수는 없다; 오직 특권이 부여된 프로세스들만이 그것을 할 수 있다. 대신에, 프로그램을 위하여 persona를 변경하기 위한 일반적인 방법은 미리 특별한 사용자나 그룹으로 변경하여 프로그램이 시작되는 것이다. 이것은 파일의 억세스 모드의 setuid 와 setgid 비트들의 함수이다. 9. 8. 5절 [Permission Bits] 참조.
억세스 가능한 파일의 setuid 비트가 설정될 때, 그 파일은 자동적으로 그 파일을 소유한 사용자로 유효 사용자 ID를 변경하여 실행된다. 그와 같이, setgid 비트가 설정된 파일은 파일의 그룹으로 유효사용자 ID를 변경하여 실행된다. 23. 5절 [Executing a File] 참조. 특별한 사용자나 그룹 ID로 변경하여 만들어진 파일은 사용자나 그룹 ID에게 완전한 억세스를 보장한다. 파일 모드와 억세스가능성에 대한 일반적인 내용은 9. 8절 [File Attributes] 참조하라.
프로세스는 유효 사용자(또는 그룹) ID는 실제 ID로 언제든지 다시 변경할 수 있다. 프로그램을 좀더 견고한 프로그램으로 만들기 위해서, 그들이 필요치 않을 때는 그들에 대한 특별한 권한을 없애야(turn off) 한다.
다음은 실제와 유효 사용자와 그룹 ID들을 읽기 위한 함수들의 상세한 설명이다. 그 기능들을 사용하기 위해서는 당신은 헤더파일 `sys/types. h'와 `unistd. h'를 포함시켜야만 한다.
데이터타입 : uid__t
데이터타입 : gid__t
함수 : uid_t getuid (void)
함수 : gid_t getgid (void)
함수 : uid_t geteuid (void)
함수 : gid_t getegid (void)
함수 : int getgroups (int count, gid_t *groups)
이 절은 프로세스의 사용자 ID(실제 그리고/또는 유효)를 변경하기 위한 함수들을 설명하고 있다. 그 기능들을 사용하기 위해서, 당신은 헤더파일 `sys/types. h'와 `unistd. h'를 포함해야만 한다.
함수 : int setuid (uid_t newuid)
EINVAL : newuid 인수의 값이 유용하지 않다.
EPERM
함수 : int setreuid (uid_t ruid, uid_t euid)
EPERM
이 절은 프로세스의 그룹 ID들(실제 와 유효)을 변경하기 위한 함수들을 설명하고 있다. 이 기능들을 사용하기 위해서, 헤더파일 `sys/types. h'와 `unistd. h'를 포함해야만 한다.
함수 : int setgid (gid_t newgid)
함수 : int setregid (gid_t rgid, fid_t egid)
함수 : int setgroups (size_t count, gid_t *groups)
함수 : int initgroups (const char *user, gid_t gid)
전형적인 setuid를 사용하는 프로그램에서 항상 특별한 억세스가 필요한 것은 아니다. 그래서 필요하지 않을 때 이 억세스를 꺼버리면, 무의식적인 억세스에 대한 빌미를 제공하지 않게된다. 만일 시스템이 저장된 사용자 ID 기능을 지원하지 않는다면, 당신은 setuid로 이것을 수행할 수 있다. 게임 프로그램이 시작할 때, 그것의 실제 사용자 ID는 jdoe이고, 유효 사용자 ID는 games이고, 그것의 저장된 사용자 ID 또한 games이다. 그 프로그램은 다음과 같이, 시작할 때 일단 사용자 ID값들을 기록하게 된다.
user_user_id = getuid ();
game_user_id = geteuid ();
그러면 setuid (user_user_id); 로 게임 파일 억세스를 끌 수 있고 setuid (game_user_id); 로 다시 켤 수 있다. 이 프로세스를 통해서, 실제 사용자 ID를 jdoe로 남고 저장된 사용자 ID는 games로 저장되기 때문에, 프로그램은 항상 다른 것으로 유효 사용자 ID를 설정할 수 있다.
저장된 사용자 ID 기능을 지원하지 않는 다른 시스템에서, 당신은 프로세스의 실제 사용자 ID 와 유효 사용자 ID를 교환하기 위해서는 setreuid를 사용해서 setuid 억세스를 켜고 끌 수 있다. 다음처럼;
setreuid (geteuid (), getuid ());
이것은 실패하지 않고_항상 허용되는 특별한 경우이다.
왜 이것은 setuid 억세스를 토글링하는 효과를 갖는가? 게임 프로그램이 단지 시작되어 있다고 가정하고, 유효 사용자 ID가 games 일 동안 실제 사용자 ID는 jdoe라고 가정하자. 이 경우, 게임은 점수 파일을 기록할 수 있다. 만일 두 개의 유효 사용자 ID 와 실제 사용자 ID를 교환한다면, 실제 사용자 ID는 games가 되고 유효 사용자 ID는 jdoe가 된다; 이제 프로그램은 오직 jdoe만 억세스를 갖는다. 다른 교환은 유효 사용자 ID를 다시 games로 되돌리고 점수 파일에 억세스를 저장한다.
시스템의 두 종류를 다루기 위해서는, 다음처럼 프리프로세서를 사용해서 저장된 사용자 ID 기능을 지원하는지 테스트해 보아야 한다.
다음은 유효 사용자 ID를 변경하는 프로그램을 어떻게 준비하는지 보여주는 예제이다. 이것은 caber-toss라고 불리는 게임 프로그램의 일부분으로써 그 게임 프로그램은 오직 게임 프로그램 그 자체에 의해서만 점수 파일을 다룰 수 있도록 되어있다. 그 게임 프로그램은 실행파일이 set-user-ID 비트로 인스톨될 것이고 실행파일이 점수파일과 같은 사용자에 의해 소유된다고 가정한다. 전형적으로, 시스템 관리자는 이 목적을 위해서 게임과 같은 계정을 준비할 것이다.
실행가능파일은 모드 4755가 주어지고, 그래서 `ls -l'을 하면 다음과 같은 출력을 얻게된다.
-rwsr-xr-x 1 games 184422 Jul 30 15: 17 caber-toss
set-user-ID 비트는 파일 모드에서 `s'로 나타난다. 점수 파일을 모드 644로 주어지고, `ls -l'하면 다음과 같은 결과를 얻는다.
다음은 어떻게 변경된 사용자 ID를 준비할 것인지를 보여주는 프로그램의 일부분이다. 이 프로그램은 만일 시스템이 저장된 ID 기능을 지원한다면 그것을 사용하고, 그렇지 않다면 setreuid를 사용해서 유효 사용자 ID와 실제 사용자 ID를 교환한다.
setuid 프로그램은 원래 의도하지 않았던 사용자 억세스를 부여하는 오류를 범하기 쉽다_실제로, 만일 당신이 이러한 문제를 피하기를 원한다면, 각별한 주의를 해야만 한다. 다음은 의도되지 않은 억세스를 막고, 만일 그것일 발생했을 때 그 영향력을 최소화하기 위한 안내지침이다.
절대적인 필요가 없는 한 root 와 같은 특권을 가진 사용자 ID들은 setuid 프로그램을 가져서는 안된다. 만일 자원(resource)이 당신의 특정한 프로그램에게 특정한 것이라면, 특권을 가지지 않은 사용자 ID 또는 그룹 ID가 단지 그 자원을 다루기만 하도록, 새롭게 정의하는 것이 좋다.
유효 사용자 ID를 변경하는것과 함께 system 함수와 exec 함수를 사용하는 것에 대해서는 주의를 해야만 한다. 당신 프로그램의 사용자가 변경된 사용자 ID를 사용하여 프로그램을 실행시키는 것을 허가하지 말아라. execlp 와 execvp 함수들도 위험을 내포하고 있다 (그들은 사용자의 PATH 환경변수에 의존하여 실행되기 때문이다. ) 만일 변경된 ID로 다른 프로그램을 실행해야만 한다면, 실행가능 파일을 절대 파일 이름( 6. 2. 2절 [File Name Resolution] 참조. ) 어로 지정하고, 실행가능 파일과 원래의 users 와 같은 모든 포함된 디렉토리들이 다른 프로그램과 함께 그것의 위치를 변경하지 못하도록 보호를 해야만 한다.
실제로 자원을 사용하는 프로그램의 일부분에서 자원을 제어하고 있는 사용자 ID만을 사용하라. 당신이 그 작업을 마쳤을 때, 실제 사용자의 사용자 ID로 유효 사용자 ID를 반환하라. 25. 8절 [Enable/Disable Setuid] 참조.
만일 당신의 프로그램에서 setuid 부분이 제어할 수 있는 자원이외에 다른 파일을 억세스할 필요가 있다면, 그와같은 파일을 억세스할 수 있는 권한을 가진 실제 사용자를 조회해야만 한다. 당신은 그와같은 일을 위해서 access 함수 ( 9. 8. 6절 [Access Permission] 참조. )를 사용할 수 있다; 그것은 유효 ID들보다는 실제 사용자와 그룹 ID들을 사용한다.
이 절에 설명된 함수들을 사용해서 프로세스를 실행시키고 있는 사용자의 이름과 현재 세션에 로그인 된 사용자의 이름을 알아낼 수 있다. getuid와 friends 함수( 25. 5절 [Reading Persona] 참조. )를 또한 참조하라. getlogin 함수는 `unistd. h'에 선언되어 있고 cuserid 와 L_cuserid는 `stdio. h'에 선언되어 있다.
함수 : char *getlogin (void)
함수 : char * cuserid (char *string)
매크로 : int L__cuserid
대부분의 경우에, 사용자를 찾아내는 환경변수 LOGNAME를 사용하는 것이 더욱 유용하다. 이것은 사용자가 제멋대로 LOGNAME 을 설정할 수 있기 때문에 정밀함에 있어서는 좀더 유연성이 있다. 22. 2. 2절 [Standard Environment] 참조.
이 절은 등록된 사용자의 데이터베이스를 검색하고 찾기 위한 모든 것에 대해서 설명한다. 데이터베이스 자체는 거의 대부분의 시스템에서 `/etc/passwd'에 보존되어 있지만, 어떤 시스템에서는 특별한 네트웍 서버에게 그것을 억세스할 수 있도록 한다.
시스템 사용자 데이터베이스를 억세스 하기 위하여 함수와 데이터 구조체들은 헤더파일 `pwd. h'에 선언되어 있다.
데이터타입 : struct passwd
char *pw_name : 사용자 로그인 이름.
char *pw_passwd : 암호화된 비밀번호(password) 문자열.
uid_t pw_uid : 사용자 ID 번호.
gid_t pw_gid : 사용자의 디폴트 그룹 ID 번호.
char *pw_gecos
char *pw_dir
char *pw_shell
특정한 사용자에 대한 정보를 알아내기 위해서는, getpwuid 또는 getpwnam을 사용해서 시스템 사용자 데이터베이스를 검색할 수 있다. 그 함수들은 헤더파일 `pwd. h'에 선언되어 있다.
함수 : struct passwd * getpwuid (uid_t uid)
함수 : struct passwd *getpwnam (const char *name)
이 절은 한 번에 한 사용자씩, 시스템에 있는 모든 사용자들의 리스트를 읽을 수 있기 위해서는 프로그램에서 어떻게 해야하는지를 설명한다. 이곳에 설명된 함수는 헤더파일 `pwd. h'에 선언되어 있다. 당신은 특정한 파일로부터 사용자 엔트리들을 읽기 위해서 fgetpwent 함수를 사용할 수 있다.
함수 : struct passwd * fgetpwent (FILE *stream)
사용자 데이터베이스의 모든 엔트리들을 검색하는 방법에는 setpwent, getpwent, 그리고 endpwent 가 있다.
함수 : void setpwent (void)
함수 : struct passwd * getpwent (void)
함수 : void endpwent (void)
함수 : int putpwent (const struct passwd *p, FILE *stream)
함수 putpwent 는 `pwd. h'에 선언되어 있다.
이 절은 등록된 그룹들의 데이터베이스를 어떻게 검색하고 찾을 것인지에 대한 모든 것이 설명되어 있다. 대부분의 시스템에서 데이터베이스 그 자체는 `/etc/group'에 보존되고 있는데, 어떤 시스템에서 특정한 네트웍 서비스는 그것에 대한 억세스를 제공한다.
시스템 그룹 데이터베이스를 억세스하기 위한 함수와 데이터구조체는 헤더파일 `grp. h'에 선언되어 있다.
데이터 타입 : struct group
char *gr_name 그룹의 이름.
gid_t gr_gid 그룹의 그룹 ID
char **gr_mem
getgrgid 나 getgrnam 을 사용해서 정해진 그룹에 대한 정보를 그룹 데이터베이스에서 찾아낼 수 있다. 그 함수들은 `grp. h'에 선언되어 있다.
함수 : struct group * getgrgid (gid_t gid)
함수 : struct group * getgrnam (const char *name)
이 절은 프로그램에서 한 번에 한 그룹씩, 시스템에 있는 모든 그룹에 대한 리스트를 어떻게 읽을 수 있는지를 설명한다. 이곳에 설명된 함수들은 헤더파일 `grp. h'에 설명되어 있다. 특정한 파일로부터 그룹 엔트리들을 읽기 위해서 fgetgrent 함수를 사용할 수 있다.
함수 : struct group * fgetgrent (FILE *stream)
그룹 데이터베이스에 있는 모든 엔트리들을 검색하는 방법은 setgrent, getgrent, 그리고 endgrent 가 있다.
함수 : void setgrent (void)
함수 : struct group * getgrent (void)
함수 : void entgrent (void)
다음은 시스템 데이터베이스 질의 함수들을 사용하는 예를 보여주는 예제 프로그램이다. 다음 프로그램은 프로그램을 실행시키고 있는 사용자에 대한 어떤 정보를 출력한다.
목차 이전 : 24. 작업 제어 다음 26. 시스템 정보