國防科技大學計算機學院在讀碩士 2004 年 8 月
系統(tǒng)測試人員負責制定測試計劃并依照測試計劃進行測試。這些測試包括功能性的測試(黑盒測試)和非功能性的測試(白盒測試)。測試人員需要良好的測試工具來輔助完成測試任務,自動化的測試工具將大幅度提高測試人員的工作效率和質(zhì)量。
本文主要通過對Linux PAM源代碼進行分析,闡述了PAM的內(nèi)部實現(xiàn)機制和怎樣在應用程序中應用PAM進行認證,以及怎樣開發(fā)PAM服務模塊。
1 引言 身份認證是操作系統(tǒng)安全的重要機制之一,系統(tǒng)通過認證機制核查用戶的身份證明,并作為用戶進入系統(tǒng)的判定條件,是防止惡意用戶進入系統(tǒng)的第一道門檻。近年來認證理論和技術(shù)得到了迅速發(fā)展,產(chǎn)生了各種認證機制,如口令機制,RSA, DCE, kerberos認證體制,S/Key和基于智能卡的身份認證等。然而,當系統(tǒng)中引入新的認證機制時,一些系統(tǒng)入口登錄服務如login, rlogin和telnet等應用程序就必須改寫以適應新的認證機制。為了解決這個問題,1995年Sun公司的Vipin Samar和 Charlie Lai提出了PAM(Pluggable Authentication Modules),并將其應用在Solaris系統(tǒng)上。PAM框架將應用程序與具體的認證機制分離,使得系統(tǒng)改變認證機制時,不再需要修改采用認證機制的應用程序,而只要由管理員配置應用程序的認證服務模塊,極大地提高了認證機制的通用性與靈活性。
現(xiàn)在大多數(shù)操作系統(tǒng)都采用PAM實現(xiàn)身份認證,有Linux系統(tǒng)的Linux-PAM和FreeBSD5.x采用的OpenPAM(FreeBSD 4.x采用的是Linux-PAM)等。它們的實現(xiàn)原理一樣,只有實現(xiàn)細節(jié)不同而已。下面從PAM的應用開發(fā)開始介紹。
2 PAM的應用開發(fā)
2.1 PAM框架概覽 PAM即可插拔認證模塊。它提供了對所有服務進行認證的中央機制,適用于login,遠程登錄(telnet,rlogin,fsh,ftp,點對點協(xié)議(PPP)),su等應用程序中。系統(tǒng)管理員通過PAM配置文件來制定不同應用程序的不同認證策略;應用程序開發(fā)者通過在服務程序中使用PAM API(pam_xxxx( ))來實現(xiàn)對認證方法的調(diào)用;而PAM服務模塊的開發(fā)者則利用PAM SPI來編寫模塊(主要是引出一些函數(shù)pam_sm_xxxx( )供PAM接口庫調(diào)用),將不同的認證機制加入到系統(tǒng)中;PAM接口庫(libpam)則讀取配置文件,將應用程序和相應的PAM服務模塊聯(lián)系起來。PAM框架結(jié)構(gòu)如圖所示。
圖 PAM框架結(jié)構(gòu)圖
其中,pamh是一個pam_handle類型的結(jié)構(gòu),它是一個非常重要的處理句柄,是PAM與應用程序通信的唯一數(shù)據(jù)結(jié)構(gòu),也是調(diào)用PAM接口庫API的唯一句柄。pam_handle數(shù)據(jù)結(jié)構(gòu)將在下面的源代碼分析一節(jié)的介紹。
另外,如上圖所示的服務模塊分auth(認證管理)、account(賬號管理)、session(會話管理)、passwd(口令管理)四種類型,各個類型模塊的作用以及配置文件的四個組成部分模塊類型、控制標志、模塊路徑、模塊參數(shù)等在很多講PAM的配置管理的文章里都有介紹,這里就不再贅述了。
2.2 在應用程序中使用PAM認證 每個使用PAM認證的應用程序都以pam_start開始,pam_end結(jié)束。PAM還提供了pam_get_item和pam_set_item共享有關(guān)認證會話的某些公共信息,例如用戶名,服務名,密碼和會話函數(shù)。應用程序在調(diào)用了pam_start ()后也能夠用這些APIs來改變狀態(tài)信息。實際做認證工作的API函數(shù)有六個(以下將這六個函數(shù)簡稱為認證API):
- 認證管理--包括pam_authenticate ()函數(shù)認證用戶,pam_setcred ()設置,刷新,或銷毀用戶證書。
- 賬號管理--包括pam_acc_mgmt ()檢查認證的用戶是否可以訪問他們的賬戶,該函數(shù)可以實現(xiàn)口令有效期,訪問時間限制等。
- 會話管理--包括pam_open_session ()和pam_close_session ()函數(shù)用來管理會話和記賬。例如,系統(tǒng)可以存儲會話的全部時間。
- 口令管理--包括pam_chauthok ()函數(shù)用來改變密碼。
下面看一個簡單的login模擬程序:
/* 使用PAM所必需的兩個頭文件*/
#include <security/pam_appl.h>
#include <security/pam_misc.h>
void main(int argc, char *argv[], char **renvp)
{
/* 初始化,并提供一個回調(diào)函數(shù) */
if ((pam_start("login", user_name, &pam_conv, &pamh)) != PAM_SUCCESS)
exit(1);
/* 設置一些關(guān)于認證用戶信息的參數(shù) */
pam_set_item(pamh, PAM_TTY, ttyn);
pam_set_item(pamh, PAM_RHOST, remote_host);
while (!authenticated && retry < MAX_RETRIES)
{
status = pam_authenticate(pamh, 0);/* 認證,檢查用戶輸入的密碼是否正確 */
}
/* 認證失敗則應用程序退出*/
if (status != PAM_SUCCESS)
{
……
exit(1);
}
/* 通過了密碼認證之后再調(diào)用賬號管理API,檢查用戶賬號是否已經(jīng)過期 */
if ((status = pam_acct_mgmt(pamh, 0)) != PAM_SUCCESS)
{
if (status == PAM_AUTHTOK_EXPIRED)
{
status = pam_chauthtok(pamh, 0); /* 過期則要求用戶更改密碼 */
if (status != PAM_SUCCESS)
exit(1);
}
}
/* 通過帳戶管理檢查之后則打開會話 */
if (status = pam_open_session(pamh, 0) != PAM_SUCCESS)
exit(status);
……
/* 建立認證服務的用戶證書*/
status = pam_setcred(pamh, PAM_ESTABLISH_CRED);
if (status != PAM_SUCCESS)
exit(status);
……
pam_end(pamh, PAM_SUCCESS); /* PAM事務的結(jié)束 */
……
}
|
從上面程序中,我們可以了解到使用PAM認證的一般流程,同時也可以看出PAM API使得使用認證的應用程序不僅不用關(guān)心底層使用的服務模塊,而且編寫起來簡潔明了得多。
有關(guān)開發(fā)使用PAM的應用程序更加詳細完整的闡述請參考The Linux-PAM Application Developers‘ Guide。
2.3 怎樣開發(fā)PAM服務模塊 首先在編寫的服務模塊的源程序里要包含下列頭文件:
#include <security/pam_modules.h>
|
PAM的服務模塊是一個一個的動態(tài)鏈接庫文件(也可以是靜態(tài)庫),PAM接口庫通過dlopen來裝載這些庫。假設源程序名為pam_module-name.c,則需要用下列命令將其編譯成動態(tài)鏈接庫:
gcc -fPIC -c pam_module-name.c
ld -x --shared -o pam_module-name.so pam_module-name.o
|
選項-fPIC是指位置無關(guān)代碼(Position Independent Code),這類代碼支持大偏移。使用--shared選項將目標代碼放進共享目標庫中。
四種類型的模塊各自要實現(xiàn)的函數(shù)如下表所示:
模塊類型 |
要實現(xiàn)的函數(shù) |
函數(shù)功能 |
認證管理 |
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) |
認證用戶 |
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) |
設置用戶證書 |
賬號管理 |
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) |
賬號管理 |
會話管理 |
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) |
打開會話 |
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) |
關(guān)閉會話 |
口令管理 |
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) |
設置口令 |
當然同一個服務模塊可以同時屬于多種類型,只要這些類型模塊要實現(xiàn)的函數(shù)都實現(xiàn)了就可以,比如PAM自帶的經(jīng)典口令認證機制模塊pam_unix.so 就可以支持四種模塊類型。
下面來看一個最簡單的pam_deny模塊的源程序pam_deny.c:
1. #define PAM_SM_AUTH
2. #define PAM_SM_ACCOUNT
3. #define PAM_SM_SESSION
4. #define PAM_SM_PASSWORD
5. #include "../../libpam/include/security/pam_modules.h"
6. /* --- 認證管理函數(shù)的實現(xiàn)--- */
7. PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,int flags,int argc
8. ,const char **argv)
9. {
10. return PAM_AUTH_ERR;
11. }
12. PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc
13. ,const char **argv)
14. {
15. return PAM_CRED_UNAVAIL;
16. }
17. /* --- 賬號管理函數(shù)的實現(xiàn) --- */
18. PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh,int flags,int argc
19. ,const char **argv)
20. {
21. return PAM_ACCT_EXPIRED;
22. }
23. /* --- 口令管理函數(shù)的實現(xiàn) --- */
24. PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc
25. ,const char **argv)
26. {
27. return PAM_AUTHTOK_ERR;
28. }
29. /* --- 會話管理函數(shù)的實現(xiàn) --- */
30. PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh,int flags,int argc
31. ,const char **argv)
32. {
33. return PAM_SYSTEM_ERR;
34. }
35. PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh,int flags,int argc
36. ,const char **argv)
37. {
38. return PAM_SYSTEM_ERR;
39. }
40. /* 模塊定義結(jié)束 */
41. /* 靜態(tài)模塊數(shù)據(jù) */
42. #ifdef PAM_STATIC
43. struct pam_module _pam_deny_modstruct = {
44. "pam_deny",
45. pam_sm_authenticate,
46. pam_sm_setcred,
47. pam_sm_acct_mgmt,
48. pam_sm_open_session,
49. pam_sm_close_session,
50. pam_sm_chauthtok
51. };
52. #endif
|
很容易看出,pam_deny模塊支持四種模塊類型。前4行包含靜態(tài)模塊的一些原型申明,第37-39行實現(xiàn)了四種模塊類型的函數(shù),因為這只是一個簡單的拒絕服務的模塊,所以這些函數(shù)只是簡單地返回認證錯或系統(tǒng)錯等PAM錯誤。最后幾行定義了該程序被編譯成靜態(tài)模塊所需的一個模塊數(shù)據(jù)結(jié)構(gòu)。
因此,PAM SPI使得服務模塊的開發(fā)也相當簡單和專一,因為服務模塊不再需要考慮和應用程序的交互,只要將自己采用的算法實現(xiàn)好就可以了。
模塊源程序可用的flags參數(shù)值和返回值的定義這里不作全面介紹,有興趣者請參考The Linux-PAM Module Writers‘ Guide。
3 PAM接口庫源代碼分析 上面我們介紹了怎樣使用PAM和怎樣開發(fā)PAM服務模塊,要想對PAM的內(nèi)部機制有個透徹的理解,還需要進一步分析PAM接口庫的代碼。下面基于Linux 的pam-0.75-40.src.rpm包所得的源代碼進行分析。
3.1 PAM接口庫主要數(shù)據(jù)結(jié)構(gòu) 先看一下PAM接口庫用到的一些主要數(shù)據(jù)結(jié)構(gòu)。pam_handle和其他幾個主要的數(shù)據(jù)結(jié)構(gòu)(見../libpam/pam_private.h)及其之間的關(guān)系如下圖所示。
其中pam_handle包含認證的用戶的token、用戶名、應用程序名、終端名等信息,以及一個service結(jié)構(gòu)(handlers);前面幾節(jié)提到的pamh句柄就是一個pam_handle結(jié)構(gòu)。service結(jié)構(gòu)包含服務模塊的相關(guān)信息,各個域的含義是:
1. module--該結(jié)構(gòu)包含裝載的模塊的名字、類型(靜態(tài)或動態(tài)模塊)、鏈接句柄(裝載模塊時的句柄)。
2. modules_allocated--分配的模塊數(shù)。
3. modules_used--已使用的模塊數(shù)。
4. handlers_loaded--是否對操作(handlers結(jié)構(gòu))進行了初始化,handlers結(jié)構(gòu)和初始化handlers見下面的介紹。
5. conf--由應用程序相對應的配置文件指定的服務模塊的handlers。
6. other--為缺省配置文件指定的服務模塊的handlers。
handlers結(jié)構(gòu)包含六個handler結(jié)構(gòu)鏈表的指針,六個指針分別對應六種不同的認證API;libpam通過這些指針找到對應模塊的SPI服務函數(shù)。如下表所示:
handler指針 |
API函數(shù) |
SPI函數(shù) |
authenticate |
pam_authenticate( ) |
pam_sm_authenticate( ) |
setcred |
pam_setcred( ) |
pam_sm_setcred( ) |
acct_mgmt |
pam_acct_mgmt( ) |
pam_sm_acct_mgmt( ) |
open_session |
pam_open_session( ) |
pam_sm_open_session( ) |
close_session |
pam_close_session( ) |
pam_sm_close_session( ) |
chauthtok |
pam_chauthtok( ) |
pam_sm_chauthtok( ) |
handler數(shù)據(jù)結(jié)構(gòu)是最直接保存服務模塊的SPI服務函數(shù)的地址及參數(shù)的結(jié)構(gòu),其包含的主要的域的含義如下:
1. (*func)--該函數(shù)指針指向handlers所裝載的服務模塊的服務函數(shù)。
2. argc、**argv--分別為*func所指向的函數(shù)的參數(shù)個數(shù)和參數(shù)列表。
3. *next--指向堆棧模塊中的下一個服務模塊的服務函數(shù)。由此指針形成所有堆棧模塊的服務函數(shù)鏈。
3.2 PAM接口庫重要內(nèi)部函數(shù)分析 PAM接口庫中有一系列_pam開頭的內(nèi)部函數(shù),那些APIs主要是調(diào)用這些內(nèi)部函數(shù)來完成其功能的。
int _pam_add_handler(pam_handle_t *pamh, int must_fail, int other, int type, int *actions, const char *mod_path, int argc, char **argv, int argvlen)
該函數(shù)負責加載服務模塊的SPI函數(shù)。這個函數(shù)代碼很長有300多行,這里就不列舉了。其主要步驟如下:
1. 根據(jù)mod_path提供的模塊路徑裝載服務模塊(dl_open)。
2. 由type確定的類型來決定要裝入的SPI函數(shù)名并找到該函數(shù)(dlsym)的地址。type類型對應配置文件中的服務模塊的四種類型標記,type的值及其對應的要裝入的SPI函數(shù)名見下表。
模塊類型 |
type |
SPI函數(shù) |
認證管理模塊 |
PAM_T_AUTH |
pam_sm_authenticate pam_sm_setcred |
會話管理模塊 |
PAM_T_SESS |
pam_sm_open_session pam_sm_close_session |
賬號管理模塊 |
PAM_T_ACCT |
pam_sm_acct_mgmt |
口令管理模塊 |
PAM_T_PASS |
pam_sm_chauthtok |
3. 新分配一個handler結(jié)構(gòu),將dlsym找到的函數(shù)的地址賦給該handler結(jié)構(gòu)的func域,并填充其他的結(jié)構(gòu)信息。
4. 將新分配的handler結(jié)構(gòu)插入到handlers的對應handler結(jié)構(gòu)鏈表中。
_pam_parse_conf_file函數(shù)負責讀并分析PAM配置文件,將相關(guān)的信息填充到pamh句柄中,并調(diào)用_pam_add_handlers加載服務模塊的服務函數(shù)。
_pam_init_handlers函數(shù)主要做handler的初始化工作,先判斷handler是否已初始化,若沒有則調(diào)用_pam_parse_conf_file分析配置文件加載服務模塊的服務函數(shù)。
_pam_dispatch_aux(pam_handle_t *pamh, int flags, struct handler *h,……)
該函數(shù)負責遍歷執(zhí)行模塊堆棧中的每一個服務模塊對應的SPI函數(shù),即執(zhí)行h指向的handler結(jié)構(gòu)鏈表中的每一個func指向的函數(shù),并返回模塊堆棧的結(jié)果值。
int _pam_dispatch(pam_handle_t *pamh, int flags, int choice)
該函數(shù)首先通過調(diào)用_pam_init_handlers將模塊調(diào)度請求轉(zhuǎn)換為指向?qū)嶋H要運行的模塊堆棧函數(shù)鏈表的指針,并將該指針傳遞給_pam_dispatch_aux函數(shù)來遍歷模塊堆棧執(zhí)行服務函數(shù)。該函數(shù)是實現(xiàn)六個認證API函數(shù)的主要部分和公共調(diào)用的函數(shù),通過choice選項來區(qū)分是哪個認證API函數(shù)。
下面分析我們最關(guān)心的部分,也即那些認證API是怎樣找到和調(diào)度配置文件中配置的服務模塊的?
3.3 PAM認證API的實現(xiàn) 為了更加清楚地說明PAM接口庫是怎樣來調(diào)度使用模塊的,下面給出了pam_authenticate函數(shù)執(zhí)行的流程圖:
其他的認證API函數(shù)(pam_open_session等)執(zhí)行過程前面五個步驟同上圖,只是在最后一步時傳遞給_pam_dispatch_aux的指針參數(shù)不同,傳遞3.1節(jié)表中每個API函數(shù)相對應的那個handler型指針,然后執(zhí)行相對應的SPI服務函數(shù)鏈。
4 小結(jié) 無論從PAM的應用開發(fā)還是它的實現(xiàn)原理來看,這個框架及其思想都是非常完美的,所以幾乎各種版本的 UNIX 系統(tǒng)都提供對 PAM 的支持。本文通過對Linux-PAM 進行了深入仔細的分析,闡述了它的內(nèi)部實現(xiàn)機制,并且講述了怎樣在應用程序中使用PAM和怎樣開發(fā)PAM服務模塊,希望能對大家做認證相關(guān)的開發(fā)工作有所幫助。
參考資料
- [Vipin Samar, Charlie Lai, 1995] Making Login Services Independent of Authentication Technologies. Sun Technical report.
- [Andrew G. Morgan, 2001] The Linux-PAM Module Writers‘ Guide. Linux-PAM Documentation.
- [Andrew G. Morgan, 2001] The Linux-PAM Module Writers‘ Guide. Linux-PAM Documentation.
|