다음 이전 차례

8. C program에 Shadow를 지원하도록 덧붙이기

C program에 Shadow를 지원하도록 덧붙이는 것은 실제적으로 매우 간단하다. 단지 문제는 /etc/shadow file에 접근하기 위해서는 program이 root(또는 SUID root)로 실행되어야 한다는 것이다.

이 것은 커다란 문제 하나를 우리에게 강요한다: SUID program을 만들 때, 매우 조심스럽게 programming하는 습관이 되어 있어야 한다. 예를 들어, program이 shell 탈출기능을 가지고 있고 이 program이 SUID root라면, 이 기능이 root 권한을 주어서는 안된다.

password를 검사해 할 수 있지만 다른 경우는 root권한으로 실행할 필요가 없는 program에 shadow 지원 기능을 덧붙임으로써, SUID program보다 훨씬 안전한 program을 만들 수 있게 한다. xlock program이 그 한 예이다.

아래 예에서, pppd-1.2.1d는 이미 SUID root로 실행하고 있으므로, shadow 지원 기능을 덧붙이는 것은 program이 더 취약하게 만들지 않을 것이다.

8.1 Header files

header file들은 /usr/include/shadow에 있다. 또한, /usr/include/shadow.h도 있다. 그러나, 이것은 /usr/include/shadow/shadow.h에 대한 symbolic link일 것이다.

shadow 지원 기능을 추가하기 위해, header file을 넣자:

#include <shadow/shadow.h>
#include <shadow/pwauth.h>

shadow code를 상황에 따라 compile하도록 compiler directive(지시자)를 쓰는 것은 종은 방법이다 (아래 예에서 보도록).

8.2 libshadow.a library

Shadow Suite을 설치할 때, libshadow.a file은 /usr/lib에 놓인다.

shadow 지원기능을 program에 넣을려면, linker에게 libshadow.a를 같이 link하도록 지시해주어야 한다.

다음처럼:

gcc program.c -o program -lshadow

어쨌든, 아래 예에서 보다시피, 대부분 거대한 program들은 Makefile을 사용하고, 우리가 고칠 LIBS=...라는 변수를 대개 쓴다.

8.3 Shadow 구조체

libshadow.a library는 /etc/shadow file로부터 얻는 정보를 spwd라는 구조체에 담는다. spwd 구조체에 대한 정의는 /usr/include/shadow/shadow.h file에 있다:


struct spwd
{
  char *sp_namp;                /* 사용자 이름 */
  char *sp_pwdp;                /* encrypt된 password */
  sptime sp_lstchg;             /* 최근 data 수정일 */
  sptime sp_min;                /* 수정작업간의 최소 날짜(결국 한번 수정한
                                   다음 언제 수정이 가능한가에 대한 대답) */
  sptime sp_max;                /* 수정작업간의 최대 날짜(password 유효기간) */
  sptime sp_warn;               /* password가 무효가 되기 전 경고하는 기간 */
  sptime sp_inact;              /* password가 무효된 뒤, 계정이 사용불능이
                                   될 때까지의 기간. */
  sptime sp_expire;             /* 날짜(계정사용불능 - 1/1/70) */
  unsigned long sp_flag;        /* 나중을 위해 비워둠 */
};

Shadow Suitesp_pwdp field에 encode된 passwd와 함께 다른 걸 넣을 수 있다. password field는 다음처럼 될 수 있다:

username:Npge08pfz4wuk;@/sbin/extra:9479:0:10000::::

이는 password에 덧붙여, /sbin/extra program이 더 심화된 인증을 위해 호출된다는 것을 의미한다. 호출되는 program은 username, 호출이유를 알려주는 switch를 받을 수 있어야 될 것이다. 자세한 걸 알고 싶다면, /usr/include/shadow/pwauth.hpwauth.c를 보기 바란다.

이것이 의도하는 바는 -두번 사용자 확인하는 데 사용할 수도 있는- 다른 현존하는(actual) 사용자 확인 방법을 수행할 수 있도록 pwauth 기능을 쓰는 것이다.

Shadow Suite의 저자는 현존하는 대부분의 program들이 이 기능을 쓰고 있지 않음은 지적하면서, Shadow Suite 차기 version에는 사라지거나, 바뀔 것이라고 한다.

8.4 Shadow 함수들

shadow.h file은 libshadow.a library에 있는 함수들의 기본형을 포함하고 있다:


extern void setspent __P ((void));
extern void endspent __P ((void));
extern struct spwd *sgetspent __P ((__const char *__string));
extern struct spwd *fgetspent __P ((FILE *__fp));
extern struct spwd *getspent __P ((void));
extern struct spwd *getspnam __P ((__const char *__name));
extern int putspent __P ((__const struct spwd *__sp, FILE *__fp));

예제에서 쓸 함수는: getspnam - spwd 구조체에서 사용자 이름을 가져오는 함수 - 이다.

8.5 Example

이것은 shadow 지원기능이 필요하지만 기본설정으로 되어 있지 않은 program에 그것을 추가하는 예제이다.

본 예제로, PAP이나 CHAP대신 /etc/passwd file에 있는 사용자이름과 password를 사용하여 PAP 인증을 수행하는 mode를 지닌, Point-to-Point Protocol Server (pppd-1.2.1d)를 들고 있다.

pppd의 이런 기능은 그리 자주 쓰이고 있지 않다. 그러나 Shadow Suite가 설치되면 이 기능은 못 쓰게 될 것이다. 왜냐하면 password는 더 이상 /etc/passwd에 있지 않기 때문이다.

ppad-1.2.1d에서 사용자 인증하는 code는 /usr/src/pppd-1.2.1d/pppd/auth.c file에 있다.

다음 code는 #include 지시자가 위치하는 file의 윗부분에 덧댈 필요가 있다. 우리는 조건지시자(conditional directive)로 #include를 둘러쌌다 (특별히 shadow 지원기능을 넣어 compile할 때만 포함하도록)


#ifdef HAS_SHADOW
#include <shadow.h>
#include <shadow/pwauth.h>
#endif

다음은 실제 code를 고치는 일이다. 아직도 auth.c file을 고치고 있다.

고치기 전의 auth.c는:


/*
 * login - Check the user name and password against the system
 * password database, and login the user if OK.
 *
 * returns:
 *      UPAP_AUTHNAK: Login failed.
 *      UPAP_AUTHACK: Login succeeded.
 * In either case, msg points to an appropriate message.
 */
static int
login(user, passwd, msg, msglen)
    char *user;
    char *passwd;
    char **msg;
    int *msglen;
{
    struct passwd *pw;
    char *epasswd;
    char *tty;

    if ((pw = getpwnam(user)) == NULL) {
        return (UPAP_AUTHNAK);
    }
     /*
     * XXX If no passwd, let them login without one.
     */
    if (pw->pw_passwd == '\0') {
        return (UPAP_AUTHACK);
    }

    epasswd = crypt(passwd, pw->pw_passwd);
    if (strcmp(epasswd, pw->pw_passwd)) {
        return (UPAP_AUTHNAK);
    }

    syslog(LOG_INFO, "user %s logged in", user);

    /*
     * Write a wtmp entry for this user.
     */
    tty = strrchr(devname, '/');
    if (tty == NULL)
        tty = devname;
    else
        tty++;
    logwtmp(tty, user, "");             /* Add wtmp login entry */
    logged_in = TRUE;

    return (UPAP_AUTHACK);
}

사용자 password는 pw->pw_passwd에 위치한다. 따라서 할 일은 getspnam 함수를 추가하는 것이 전부다. 이 함수는 spwd->sp_pwdp에 password를 할당한다.

우리는 다른 현존하는(actual) 사용자 확인 작업을 수행하도록 pwauth 함수를 넣을 것이다. 이는 shadow file에 설정되어 있으면 자동적으로 두번째 인증을 수행한다.

shadow를 지원하도록 고친 auth.c는:


/*
 * login - Check the user name and password against the system
 * password database, and login the user if OK.
 *
 * This function has been modified to support the Linux Shadow Password
 * Suite if USE_SHADOW is defined.
 *
 * returns:
 *      UPAP_AUTHNAK: Login failed.
 *      UPAP_AUTHACK: Login succeeded.
 * In either case, msg points to an appropriate message.
 */
static int
login(user, passwd, msg, msglen)
    char *user;
    char *passwd;
    char **msg;
    int *msglen;
{
    struct passwd *pw;
    char *epasswd;
    char *tty;

#ifdef USE_SHADOW
    struct spwd *spwd;
    struct spwd *getspnam();
#endif

    if ((pw = getpwnam(user)) == NULL) {
        return (UPAP_AUTHNAK);
    }

#ifdef USE_SHADOW
        spwd = getspnam(user);
        if (spwd)
                pw->pw_passwd = spwd->sp-pwdp;
#endif
 
     /*
     * XXX If no passwd, let NOT them login without one.
     */
    if (pw->pw_passwd == '\0') {
        return (UPAP_AUTHNAK);
    }
#ifdef HAS_SHADOW
    if ((pw->pw_passwd && pw->pw_passwd[0] == '@'
         && pw_auth (pw->pw_passwd+1, pw->pw_name, PW_LOGIN, NULL))
        || !valid (passwd, pw)) {
        return (UPAP_AUTHNAK);
    }
#else
    epasswd = crypt(passwd, pw->pw_passwd);
    if (strcmp(epasswd, pw->pw_passwd)) {
        return (UPAP_AUTHNAK);
    }
#endif

    syslog(LOG_INFO, "user %s logged in", user);

    /*
     * Write a wtmp entry for this user.
     */
    tty = strrchr(devname, '/');
    if (tty == NULL)
        tty = devname;
    else
        tty++;
    logwtmp(tty, user, "");             /* Add wtmp login entry */
    logged_in = TRUE;

    return (UPAP_AUTHACK);
}

주의해서 보면 우리가 한 다른 변화를 볼 수 있을 것이다. /etc/passwd file에 password가 없다면, 원 version은 UPAP_AUTHACK를 돌려주고 접속을 허용했다. 이건 좋다. 왜냐하면, 이 login기능의 일반적인 용도는 PPP process에 접근한 다음, PAP에 의해 지원되는 사용자 이름과 password를 /etc/passwd에 있는 사용자 이름과 /etc/shadow에 있는 password와 맞는지 점검하도록 허용하는, 한 계정을 사용하는 것이기 때문이다.

따라서, 원 version이 사용자(특히, ppp)를 위해 shell을 실행시키도록 설정했다면, 누구든지 그들의 PAP를 사용자이름을 ppp, password를 null로 함으로써 ppp 연결을 획득할 수 있었다.

우리는 이것을 password가 없다면 UPAP_AUTHACK대신 UPAP_AUTHNAK를 되돌려주도록 고쳤다.

흥미롭게도 pppd-2.2.0로 같은 문제를 지니고 있다.

다음은 두가지 일이 일어날 수 있도록 Makefile을 고지는 것이다: USE_SHADOW가 선언되어 있어야 하고, libshadow.a가 link되도록 할 필요가 있다.

Makefile에서는:

LIBS = -lshadow

그리고나서 다음 줄을:

COMPILE_FLAGS = -I.. -D_linux_=1 -DGIDSET_TYPE=gid_t

에서:

COMPILE_FLAGS = -I.. -D_linux_=1 -DGIDSET_TYPE=gid_t -DUSE_SHADOW
로 바꾼다.

이제 만들어서 설치하라.


다음 이전 차례