次のページ 前のページ 目次へ

8. Cプログラムを Shadow Suite に対応させる方法

プログラムに shadow サポートを加えるのは実際にはとても簡単です。問題は /etc/shadow ファイルにアクセスするためにプログラムはroot権限 で実行するか、rootに SUID して実行しなければならないことです。

これは重大な問題です。SUID するプログラムを作る時には非常に慎重にプロ グラムする必要があります。例えば、シェルにエスケープできるプログラムは プログラムがrootに SUID されていてもrootとして実行してはなりません。

パスワードのチェックはするが、それ以外にはrootとして動作する必要がな いような場合で shadow サポートをプログラムに追加する時は shadow グルー プに SGID する方がずっと安全です。xlock プログラムはこのような例の典型 です。

以下で示す例の pppd-1.2.1d は既にrootに SUID されているので、shadow サ ポートを加えることで、プログラムがセキュリティ的により脆弱になることは もはやありません。

8.1 ヘッダファイル

ヘッダファイルは /usr/include/shadow ディレクトリ内にあるべき です。/usr/include/shadow.h も必要ですが、これは /usr/include/shadow/shadow.h へのシンボリックリンクになります。

プログラムに shadow サポートを加えるためには次のヘッダファイルをインク ルードする必要があります:

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

shadow 用のコードを条件コンパイルで利用できるようにコンパイラ命令を用 いるのは良い考えです。(以下の例でもそうしています。)

8.2 libshadow.a ライブラリ

Shadow Suite をインストールする時には libshadow.a も 作成され、/usr/lib にインストールされます。

プログラムで shadow サポートするためには、リンカに libshadow.a をリンクするように指示する必要があります。

これは以下のように行います:

gcc program.c -o program -lshadow

しかし、以下の例でわかるように大規模なプログラムでは大抵 Makefile を使いますから、普通は LIBS 変数を変更しま す。

8.3 Shadow 構造体

libshadow.a ライブラリは spwd と呼ばれる構造体に /etc/shadow ファイルから取り出した情報を格納します。これは ヘッ ダファイル /usr/include/shadow/shadow.h における spwd の定義です:


struct spwd
{
  char *sp_namp;                /* login name */
  char *sp_pwdp;                /* encrypted password */
  sptime sp_lstchg;             /* date of last change */
  sptime sp_min;                /* minimum number of days between changes */
  sptime sp_max;                /* maximum number of days between changes */
  sptime sp_warn;               /* number of days of warning before password
                                   expires */
  sptime sp_inact;              /* number of days after password expires
                                   until the account becomes unusable. */
  sptime sp_expire;             /* days since 1/1/70 until account expires
*/
  unsigned long sp_flag;        /* reserved for future use */
};

Shadow Suite では sp_pwdp に単なるエンコードされたパ スワードだけでなく、それ以外の情報も持たせることができます。例えば、パ スワードフィールドが以下のような行を含んでいる場合です:

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

これで、パスワードに加えて/sbin/extra プログラムをさらなる認 証に用いることを指示しています。呼び出されたプログラムは、ユーザ名とな ぜ呼び出されたかを示すスイッチを渡されます。より詳しい情報を得るためには /usr/include/shadow/pwauth.h とソースコードに含まれる pwauth.c を読んでください。

これが意味するところは、2次認証に注意することと、実際の認証を行う時に は関数 pwauth を用いるべきだということです。以下の例題ではこ れを実行しています。

現在存在しているプログラムのほとんどがこれを行っていないため、 Shadow Suiteの作者は将来のバージョンではこの機能を無くすか仕 様を変更することを言っています。

8.4 Shadow サポートのための関数

shadow.h ファイルには libshadow.a ライブラリが含んで いる関数の関数プロトタイプも書かれています:


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 例題

これはデフォルトで shadow サポートをしていないプログラムを shadow 対応 させる例です。

この例では Point-to-Point プロトコルサーバ(pppd-1.2.1d) を用 いています。このプログラムは PAPCHAP ファイルで なく /etc/passwd ファイルから得たユーザ名とパスワードを用いて PAP 認証を行うモードを持っています。既に pppd-2.2.0 で shadow サポートが行われているので、pppd-2.2.0 に対して例題のコードを追加する 必要はありません。

pppd のこの機能はあまり使わないものですが、Shadow Suite をイ ンストールするとパスワードが /etc/passwd に保持されなくなるた めに、この機能は全く使えなくなってしまいます。

pppd-1.2.1d のユーザ認証の部分のコードは /usr/src/pppd-1.2.1d/pppd/auth.c ファイルにあります。

以下のコードはコード内の他の #include 命令よりも前に加える必 要があります。条件命令で #include を囲んでいます(したがって shadow サポートありでコンパイルする時だけインクルードされます)。


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

次の部分は実際のコードに対する変更点です。auth.c ファイルに更 に変更を加えます。

変更前の 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);
}

ユーザのパスワードは pw->pw_passwd に代入されているので、ここ で行う必要があるのは関数 getspnam を追加することです。この関 数はパスワードを spwd->sp_pwdp に代入します。

次に、実際の認証を行うために関数 pwauth を加えます。この関数 は shadow ファイルが2次認証をするように設定されている場合には、自動的 に2次認証を実行します。

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ファイル内にパスワードがない場合にはアク セスを許します。(UPAP_AUTHACK を戻し値にする。)これはあまり良くないこ とです。普通のログインでは PPP プロセスへのアクセスを許す時に一つのア カウントを用い、それから /etc/passwd ファイルのユーザ名と /etc/shadowファイルのパスワードを利用して、入力されたユーザ名 とパスワードに対して PAP 認証を行うからです。

だから、もし元のバージョンをユーザ(例えば ppp)のシェルとして走らせると、 ユーザ ppp で空パスワードにして PAP を設定しても誰も PPP 接続 を得ることができなくなります。

パスワードが空の時には UPAP_AUTHNAK でなく UPAP_AUTHACKを戻し値とするようにすることでも修正できます。

面白いことに、pppd-2.2.0 にも同じ問題があります。

次に、以下の2点について Makefile を修正する必要があります: USE_SHADOW を定義することと、libshadow.a をリンク するようにすることです。

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

最後に、コンパイル及びインストールを実行しましょう。


次のページ 前のページ 目次へ