新增支援 shadow 程式事實上是很直接的。 唯一的問題是程式需要以 root (或 SUID root)
權限執行,這樣才可以存取 /etc/shadow
檔。
這顯示一個大問題: 當建立 SUID 程式時需要很小心依照程式運作。舉例說明: 如果以個 程式有 shell escape,如果程式本身是 SUID root 將不需要以 root 方式呈現。
對程式新增支援 shadow 而言,它可以檢查密碼,但不需以 root 權限執行,而是以 SUID
shadow 取代執行比較安全。 xlock
程式就是一個例子。
接下來範例介紹, pppd-1.2.1d
已經以 SUID as root 方式執行,所以新增 shadow
支援應該不會使程式產生任何影響。
標頭檔應存在 /usr/include/shadow
。 應該有一個 /usr/include/shadow.h
檔,
但是它將 symbolic link 到 /usr/include/shadow/shadow.h
。
為了新增支援 shadow 程式,你需要 include 標頭檔:
#include <shadow/shadow.h> #include <shadow/pwauth.h>
當你安裝 Shadow Suite, libshadow.a
檔被建立和安裝在
/usr/lib
目錄。
當編譯一個 shadow support 程式,linker 需要包括 libshadow.a
函式庫進入鏈結。
執行如下:
gcc program.c -o program -lshadow
然而,就像我們接下來要看的例子,大部分大程式使用 Makefile
且
通常有變數呼叫 LIBS=...
需要被修改。
libshadow.a
函式庫對它從 /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
程式應該被更多的權限呼叫。
程式的呼叫需取得使用者名稱和指出為何需被呼叫的 switch才可通過。 查看
/usr/include/shadow/pwauth.h
和原始碼 pwauth.c
獲得更多資訊。
為何我們應使用 pwauth
去表示真正的權限,這是什麼意思,它將使第二組權限也
跑得很好。
Shadow Suite 作者指出因為大部分存在的程式都不這麼作囉,所以 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 支援程式,但預設值並沒有。
本範例使用 Point-to-Point Protocol Server (pppd-1.2.1d),它有個模式是表示
從 /etc/passwd
檔取代 PAP 或 CHAP 檔使用帳號密碼的 PAP
權限,你將不需要在 pppd-2.2.0
加這些程式碼,因為它已經存在囉。
pppd 的未來大致上不會被使用很多,但是如果你安裝 Shadow Suite,儲存在
/etc/passwd
檔的密碼將無法運作。
在 pppd-1.2.1d
權限使用的程式碼是位在 /usr/src/pppd-1.2.1d/pppd/auth.c
檔。
接下來程式碼需要被加在所有其他 #include
指令檔案的最上頭,我們將注意有環境指令的
#includes
。
#ifdef HAS_SHADOW #include <shadow.h> #include <shadow/pwauth.h> #endif
接下來要做的事情是變更實際碼, 我們將變更 auth.c
檔。
變更前 auth.c
檔 function 為:
/* * 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
function,這將會把密碼放到 spwd->sp_pwdp
。
我們將新增 pwauth
function 來表示真正的權限。 這將在 shadow 檔設定時
自動產生第二個權限。
變更為可以支援 shadow 後的 auth.c
function:
/* * 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 process的帳號,然後檢查帳號密碼,該帳號密碼是由
RAP 、在 /etc/passwd
檔的帳號和 /etc/shadow
檔的密碼供應。
所以如果我們設定原本版本對每個使用者,如 ppp
可以在 shell 執行,然後任何人可以
獲得 ppp 鏈結透過設定他們對使用者 ppp
的 PAP 和 null 的密碼。
我們修正 UPAP_AUTHNAK
取代
UPAP_AUTHACK
如果密碼欄位是空的。
有趣的是 pppd-2.2.0
有相同的問題。
接下來我們需要變更 Makefile 以便讓兩件事發生:
USE_SHADOW
必須被重新定義且libshadow.a
需要被新增到鏈結 process。
編輯 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
現在執行 make 跟 install.