- 論壇徽章:
- 0
|
學(xué)習(xí)使用可插拔驗證模塊(Pluggable Authentication Modules,PAM)進行用戶身份驗證,以實現(xiàn)身份驗證機制與應(yīng)用程序的分離。
內(nèi)容
簡介
在
本系列的第1篇文章中
,我們討論了基于口令的用戶驗證的基礎(chǔ)知識。我們給出了身份驗證 和授權(quán) 這兩個術(shù)語的定義, 討論了基于本地文件的口令儲存和加密算法,并介紹了 Solaris 操作系統(tǒng)提供的讀取和加密口令的 API 函數(shù)。最后,我們通過一個示例程序展示了要求用戶輸入口令和比較口令的過程。
Solaris 操作系統(tǒng)為用戶驗證提供了一個可擴展的機制,這就是可插拔驗證模塊框架。在本文中,我們給出了 PAM 的概述并開發(fā)一個基于 PAM 的程序,該程序在本系列的第1篇文章中有詳細的描述。盡管在應(yīng)用程序中使用 PAM 進行用戶驗證會增加復(fù)雜性,但是 PAM 能有效的實現(xiàn)驗證機制和應(yīng)用程序的分離。這也就意味著,無需修改應(yīng)用程序就能加入新的驗證機制(例如,生物特征掃描)或修改現(xiàn)有的驗證機制。
PAM概述
file:///im/a.gif
PAM 的結(jié)構(gòu)主要由兩部分組成:PAM 庫,它提供了 API 和 SPI( 服務(wù)提供者接口 ),和各種可堆疊的服務(wù)模塊。配置文件 /etc/pam.conf 是用于配置各種系統(tǒng)服務(wù)的服務(wù)模塊,例如 login 或 cron。應(yīng)用程序通過 API 與 PAM 進行通信,而服務(wù)模塊則使用的是 SPI。
PAM服務(wù)模塊
file:///im/a.gif
PAM 服務(wù)模塊 是一個共享庫,它提供了一個或多個以下的驗證和安全服務(wù):
- 驗證服務(wù)模塊。用于驗證用戶訪問帳戶或服務(wù)的權(quán)限,并建立用戶憑證。
- 帳戶管理模塊。用于確定當(dāng)前用戶帳號的有效性。例如,該模塊能夠檢查口令或帳戶的有效期以及規(guī)定的訪問時間限制。
- 會話管理模塊。用于設(shè)置和終止登錄會話。
- 口令管理模塊。用于強制實施口令強度規(guī)則,并執(zhí)行驗證令牌的更新。
PAM 模塊可以實現(xiàn)其中的一項或多項服務(wù)。將簡單模塊用于明確定義的任務(wù)中可以增加配置靈活性。因此,應(yīng)該在不同的模塊中實現(xiàn) PAM 服務(wù)。然后,可以按照 /etc/pam.conf文件中定義的方式根據(jù)需要使用這些服務(wù)。
PAM 配置文件
file:///im/a.gif
我們上面說的 PAM 結(jié)構(gòu)是通過配置文件 /etc/pam.conf來配置的,系統(tǒng)管理員可以管理該文件。在 pam.conf 中每個服務(wù)系統(tǒng)可以有一個或多個條目。在 pam.conf 中這些條目的順序是十分重要的,因為錯誤的配置 pam.conf 文件會導(dǎo)致在多用戶的模式下所有系統(tǒng)用戶的死鎖。系統(tǒng)服務(wù)不能用自己的集合項來使用 "其他" 服務(wù)。
該配置文件中的條目將采用以下格式,其中前面四個是必須的。
- Service name。服務(wù)名稱,例如 cron、login 和 passwd。應(yīng)用程序可以針對其提供的服務(wù)使用不同的服務(wù)名。服務(wù)名 other 是用作通配符服務(wù)名的預(yù)定義名稱。如果在配置文件中未找到特定的服務(wù)名,則會使用 other 配置。 例如,Solaris 安全 shell 守護進程 sshd 并用 sshd 做為預(yù)驗證方法的關(guān)鍵字。(更詳細的內(nèi)容,請參考 sshd 手冊的 SECURITY 一節(jié))
- Module type。 服務(wù)模塊類型,即 account、auth、password 或 session。account 服務(wù)模塊用于驗證賬戶的有效性(例如,口令,賬戶的有效期,訪問時間限制),auth 服務(wù)模塊用于驗證賬戶的訪問和建立用戶憑證,password服務(wù)模塊用于管理用戶口令的修改,session 服務(wù)模塊用于建立和取消登錄會話。
- Control flag。 控制標(biāo)志。用于指明在確定服務(wù)的集成成敗值的過程中模塊所起的作用。有效的控制標(biāo)志包括 binding、optional、required、requisite 和 sufficient。我們將在下一個部分中將詳細的討論這些內(nèi)容。
- Module path。 用于實現(xiàn)服務(wù)的庫對象的路徑。如果路徑名不是絕對路徑名,則假設(shè)路徑名相對于 /usr/lib/security/$ISA,通過使用與體系結(jié)構(gòu)有關(guān)的宏 $ISA ,可查看應(yīng)用程序特定體系結(jié)構(gòu)的目錄。
- Module options。傳遞給服務(wù)模塊的選項。模塊的手冊頁介紹了相應(yīng)模塊可接受的選項。
PAM 堆棧模塊
file:///im/a.gif
上面提到過應(yīng)用程序能夠調(diào)用多個服務(wù)模塊,并且每個模塊的條目都在配置文件 /etc/pam.conf 中。應(yīng)用程序調(diào)用以下函數(shù)時,PAM 庫將讀取配置文件 pam.conf 以確定針對該服務(wù)參與操作的模塊:
pam_authenticate
pam_acct_mgmt
pam_setcred
pam_open_session
pam_close_session
pam_chauthtok
如果服務(wù)在配置文件 pam.conf 中只有一個條目,那么模塊的結(jié)果決定了用了該種服務(wù)的操作的結(jié)果。然而,如果服務(wù)在配置文件pam.conf 中定義了多個項,那么模塊將采用堆棧的形式,操作的結(jié)果取決于集成的處理,該處理考慮服務(wù) PAM 棧中所有模塊的結(jié)果(除非模塊被過早的被終止了)。
對于一個給定的服務(wù),每個出現(xiàn)在配置文件 pam.conf 中的模塊都將順序的執(zhí)行。因為控制標(biāo)識決定了模塊的值,所以無論成功或失敗模塊都被集成到了整個的操作結(jié)果中:
- binding。如果前面所需的模塊都沒有失敗,則成功滿足綁定模塊的要求后,會向應(yīng)用程序立即返回成功信息。如果滿足這些條件,則不會進一步執(zhí)行模塊。如果失敗,則會記錄 "所需的失敗" 信息并繼續(xù)處理模塊。
- optional。不必成功滿足可選模塊"的要求即可使用服務(wù)。如果失敗,則會記錄可選的失敗信息。
- required。必須成功滿足必需模塊的要求才能使用服務(wù)。如果失敗,,則執(zhí)行該服務(wù)的其余模塊后會返回a "錯誤"信息.僅當(dāng)綁定模塊或所需的模塊沒有報告失敗的情況下,才會返回該服務(wù)最終成功的信息。
- requisite。必須成功滿足必備模塊的要求才能使用服務(wù)。如果失敗,則會立即返回 "錯誤",而不會進一步執(zhí)行模塊。只有一個服務(wù)的所有必備模塊都返回成功,函數(shù)才能向應(yīng)用程序返回成功。
- sufficient。如果該模塊失敗,將標(biāo)記為 "選擇失敗"。如果前面所需模塊的要求都成功滿足,則成功滿足控制標(biāo)志為 suffcient 的模塊的要求后將向應(yīng)用程序立即返回成功,而不會進一步執(zhí)行模塊。如果失敗,則會記錄可選的失敗信息。
前面的表述讓人費解,因此讓我們來看配置文件pam.conf, 的一個示例,該示例講述的是rlogin服務(wù)。下面是它在文件 pam.conf中的條目:
rlogin auth sufficient pam_rhosts_auth.so.1
rlogin auth requisite pam_authtok_get.so.1
rlogin auth required pam_dhkeys.so.1
rlogin auth required pam_unix_cred.so.1
rlogin auth required pam_unix_auth.so.1
rlogin 服務(wù)請求驗證的時候,將首先執(zhí)行(pam_rhosts_auth)模塊。對于該模塊,控制標(biāo)志將設(shè)置為sufficient。如果pam_rhosts_auth 模塊可以驗證用戶,則處理停止并且會向應(yīng)用程序返回成功信息。
如果 pam_rhosts_auth 模塊無法驗證用戶,則執(zhí)行下一個模塊(pam_authtok_get) 。此模塊的控制標(biāo)志將設(shè)置為 requisite。如果 pam_authtok_get失敗,則驗證過程將結(jié)束并向應(yīng)用程序返回失敗信息。如果pam_authtok_get成功,則將執(zhí)行接下來的三個模塊(pam_dhkeys、pam_unix_cred 和 pam_unix_auth) 。這三個模塊的關(guān)聯(lián)控制標(biāo)志都會設(shè)置為 required,這也意味著,如果應(yīng)用程序返回了成功信息這三個模塊都必須順序的返回驗證成功信息。然而,只要有其中的一個模塊失敗,失敗就會被記錄并且只有三個模塊執(zhí)行完畢之后該記錄才會返回給應(yīng)用程序。
此時,最后一個模塊 pam_unix_auth 已經(jīng)執(zhí)行完畢,成功或失敗的信息會返回給應(yīng)用程序,如果失敗則會拒絕用戶通過rlogin進行訪問。
一些 API 函數(shù)
file:///im/a.gif
既然我們已經(jīng)描述了 PAM 框架和它的配置,接著讓我們來關(guān)注下一些 PAM 的 API函數(shù)。對于一個非常簡單的 PAM 應(yīng)用程序,有3個需要考慮的函數(shù): pam_start、pam_end 和 pam_authenticate。
pam_start 函數(shù)
pam_start 函數(shù)用于初始化一個 PAM 驗證事務(wù)。
#include
int pam_start (const char service, const char user,
const struct pam_conv pam_conv, pam_handle_t pamh);
這個函數(shù)初始化一個服務(wù)和用戶驗證事務(wù),服務(wù)和用戶分別由 service 和 user 表示。 參數(shù) pam_conv 指定了將使用的會話函數(shù)。一旦成功 pamh 就指向一個句柄,該句柄必須由隨后的 PAM 函數(shù)使用,與文件描述符用于文件的方式類似。我們在本系列的下一個部分討論會話函數(shù),但是顧名思義,會話函數(shù)是用于處理與用戶的會話(例如,提示用戶輸入口令)。
pam_end 函數(shù)
The pam_end 函數(shù)用于終止一個 PAM 驗證事務(wù)
#include
int pam_end (pam_handle_t *pamh, int status);
由 pamh 表示的 PAM 驗證事務(wù)被終止,并且參數(shù) status 被傳遞給存儲在PAM句柄的清理函數(shù)(如果有的話)。
pam_authenticate 函數(shù)
pam_authenticate 函數(shù)用于驗證當(dāng)前的用戶
#include
int pam_authenticate (pam_handle_t *pamh, int flags);
該函數(shù)用于驗證當(dāng)前的用戶。用戶通過 pam_start 調(diào)用創(chuàng)建句柄 pamh 時指定了該函數(shù)。用戶驗證的過程是與模塊相關(guān)的,但是驗證通常涉及詢問用戶的口令,提示用戶輸入以前的口令或令牌,或者驗證用戶身份的其他一些方法。其他的驗證機制包括生物特征掃描和智能卡。參數(shù) flags 可用于設(shè)置各種驗證服務(wù)的標(biāo)志。
一個 PAM 應(yīng)用程序的例子
file:///im/a.gif
我們已經(jīng)描述了足夠的資料,現(xiàn)在來看一個使用 PAM 驗證方式的樣例應(yīng)用程序。和以前的文章一樣,該示例將不停地詢問當(dāng)前用戶(由這個過程中的真實用戶 ID 決定)的口令直到輸入了正確的口令,如何正確口令則結(jié)束程序。下面是程序的源代碼。
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 extern int check_conv (int num_msg, struct pam_message **msg,
9 struct pam_response *resp, void *app_data);
10 int main (void)
11 {
12 struct passwd *pwd_info;
13 struct pam_conv conv = {check_conv, NULL};
14 pam_handle_t *ph;
15 int error;
16 if ((pwd_info = getpwuid (getuid ())) == NULL) {
17 fprintf (stderr, "Call to getpwuid failed\n");
18 exit (1);
19 }
20 if ((error = pam_start ("check_pass", pwd_info -> pw_name,
21 &conv, &ph)) != PAM_SUCCESS) {
22 fprintf (stderr, "Call to pam_start failed: %s\n",
23 pam_strerror (ph, error));
24 exit (1);
25 }
26 for (;;) {
27 error = pam_authenticate (ph, 0);
28 if (error == PAM_SUCCESS) {
29 printf ("Passwords match.\n");
30 break;
31 } else {
32 printf ("Passwords don't match.\n");
33 }
34 }
35 pam_end (ph, 0);
36 return (0);
37 }
我們來仔細察看這段共有 37 行的代碼。
1-9: 包括頭文件和聲明會話函數(shù)。盡管在本文中沒有描述會話函數(shù),為了那些想現(xiàn)在就進行體驗的讀者,我們給出了這個例子中使用的函數(shù)。
16-19: 調(diào)用 getpwuid 以獲得由這個過程中的真實用戶 ID 標(biāo)識的當(dāng)前用戶的口令文件信息。我們可以轉(zhuǎn)而使用 getlogin 或 cuserid。但是,有安全意識的程序應(yīng)該避免使用這些函數(shù),因為它們依賴并且信任 /var/adm/utmpx 的內(nèi)容。 在一些UNIX平臺上,盡管不是在 Solaris 平臺上,這些文件是所有人可寫的,因此一個不懷好意的用戶可能改變文件中針對某個授權(quán)用戶的項, 而我們的程序并不知曉。我們這樣做是因為在下一步我們必須將用戶的用戶名傳遞給 pam_start。
20-25: 調(diào)用 pam_start 初始化 PAM 會話,使用 "check_pass" 作為服務(wù)名和確定的用戶名。 注意,由于正在使用的服務(wù)名在默認(rèn)的路徑 /etc/pam.conf, 下不存在,因此將使用other 服務(wù)的驗證條目。如果有某個錯誤發(fā)生,通知用戶并且退出。
26-34: 在無限循環(huán)中,使用調(diào)用 pam_start 時建立的會話函數(shù)調(diào)用 pam_authenticate 來驗證用戶的身份。簡單地說,正是這個會話函數(shù)提示用戶輸入口令和從用戶那里讀取口令。 如果 pam_authenticate返回 PAM_SUCCESS,則打印一條消息通知口令匹配并且退出該循環(huán)。 否則,打印一條失敗消息然后重試。
35: 調(diào)用 pam_end 終止 PAM 事務(wù)。
下面是會話函數(shù)的源代碼。(我們將在本系列的下一篇文章中詳細的討論它們。)
1 #include
2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 int check_conv (int num_msg, struct pam_message **msg,
9 struct pam_response **resp, void *app_data)
10 {
11 struct pam_message *m = *msg;
12 struct pam_response *r;
13 int i;
14 char *ct_passwd;
15 if ((num_msg = PAM_MAX_NUM_MSG)) {
16 fprintf (stderr, "Invalid number of messages\n");
17 *resp = NULL;
18 return (PAM_CONV_ERR);
19 }
20 if ((*resp = r = calloc (num_msg, sizeof (struct pam_response))) == NULL)
21 return (PAM_BUF_ERR);
22 for (i = 0; i msg_style) {
24 case PAM_PROMPT_ECHO_OFF:
25 ct_passwd = getpassphrase ("Enter password: ");
26 r->resp = strdup (ct_passwd);
27 m++;
28 r++;
29 break
30 case PAM_PROMPT_ECHO_ON:
31 if (m->msg)
32 fputs (m->msg, stdout);
33 r->resp = NULL;
34 m++;
35 r++;
36 break;
37 case PAM_ERROR_MSG:
38 if (m->msg)
39 fprintf (stderr, "%s\n", m->msg);
40 m++;
41 r++;
42 break;
43 case PAM_TEXT_INFO:
44 if (m->msg)
45 printf ("%s\n", m->msg);
46 m++;
47 r++;
48 break;
49 }
50 return (PAM_SUCCESS);
51 }
52 }
在編譯這個程序之后,運行并看看會發(fā)生些什么。
rich@marrakesh4112# make pam_check_pass
cc -o pam_check_pass -lpam pam_check_pass.c pam_check_pass_conv.c
pam_check_pass.c:
pam_check_pass_conv.c:
rich@marrakesh4113# ./pam_check_pass
Enter password:
Passwords don't match.
Enter password:
Passwords don't match.
注意,不管我們輸入什么口令,應(yīng)用程序都提示口令不匹配。這是因為用戶 "rich" 沒有適當(dāng)?shù)臋?quán)限。在后臺,我們需要讀取影子文件,以比較輸入口令是不正確。這是因為 "rich" 是一個本地用戶,并且只有根用戶可以讀取影子文件 (/etc/shadow)。我們切換為根用戶并重試。
rich@marrakesh4114# su
Password:
# ./pam_check_pass
Enter password:
Passwords don't match.
Enter password:
Passwords match.
這次,在第一次輸入一個錯誤口令后,我們成功了。注意,使用 PAM 時并不能使用最低權(quán)限這個選項。因為要求使用全域權(quán)限。
和以前一樣,可以很容易將該示例修改為一個簡單的終端鎖定程序。同樣的,這也是留給讀者的一個練習(xí)
結(jié)束語
file:///im/a.gif
在本文開頭,我們簡要介紹 PAM 的概況,然后討論了 PAM 框架的各個部分。我們圍繞 PAM 服務(wù)模塊、PAM 配置文件 /etc/pam.conf,以及如何開發(fā)服務(wù)模塊棧展開了探討。
然后,我們描述了PAM API 中的一些重要函數(shù):pam_start, pam_end, 和 pam_authenticate。最后,我們編寫了一個示例程序?qū)⑦@些都聯(lián)系起來:支持 PAM 的口令比對程序,該程序在本系列中的第一篇文章中有詳細的描述。
在本系列的下一篇文章中,我們將介紹如何編寫一個 PAM 會話程序,并了解一些其它的 API 函數(shù)。
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u/23363/showart_2154420.html |
|