プログラムに shadow サポートを加えるのは実際にはとても簡単です。問題は
/etc/shadow ファイルにアクセスするためにプログラムはroot権限
で実行するか、rootに SUID して実行しなければならないことです。
これは重大な問題です。SUID するプログラムを作る時には非常に慎重にプロ グラムする必要があります。例えば、シェルにエスケープできるプログラムは プログラムがrootに SUID されていてもrootとして実行してはなりません。
パスワードのチェックはするが、それ以外にはrootとして動作する必要がな いような場合で shadow サポートをプログラムに追加する時は shadow グルー プに SGID する方がずっと安全です。xlock プログラムはこのような例の典型 です。
以下で示す例の pppd-1.2.1d は既にrootに SUID されているので、shadow サ ポートを加えることで、プログラムがセキュリティ的により脆弱になることは もはやありません。
ヘッダファイルは /usr/include/shadow ディレクトリ内にあるべき
です。/usr/include/shadow.h も必要ですが、これは
/usr/include/shadow/shadow.h へのシンボリックリンクになります。
プログラムに shadow サポートを加えるためには次のヘッダファイルをインク ルードする必要があります:
#include <shadow/shadow.h> #include <shadow/pwauth.h>
shadow 用のコードを条件コンパイルで利用できるようにコンパイラ命令を用 いるのは良い考えです。(以下の例でもそうしています。)
Shadow Suite をインストールする時には libshadow.a も
作成され、/usr/lib にインストールされます。
プログラムで shadow サポートするためには、リンカに
libshadow.a をリンクするように指示する必要があります。
これは以下のように行います:
gcc program.c -o program -lshadow
しかし、以下の例でわかるように大規模なプログラムでは大抵
Makefile を使いますから、普通は LIBS 変数を変更しま
す。
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の作者は将来のバージョンではこの機能を無くすか仕 様を変更することを言っています。
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 構造体を与える)です。
これはデフォルトで shadow サポートをしていないプログラムを shadow 対応 させる例です。
この例では Point-to-Point プロトコルサーバ(pppd-1.2.1d) を用
いています。このプログラムは PAP や CHAP ファイルで
なく /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
最後に、コンパイル及びインストールを実行しましょう。