介紹PAM

創建時間:2000-06-08
文章屬性:整理
文章來源:原文BY - P H R A C K   M A G A Z I N E -56-13.html
文章提交:xundi (xundi_at_xfocus.org)

介紹PAM

BY XUNDI 2000-05-08
本人水平有限,錯誤在所難免,盡請指教[email protected]
原文BY - P H R A C K   M A G A Z I N E -56-13.html


----|    介紹
此文主要介紹了PAM模塊的知識,最後我還加上了一個PAM的簡單使用例子,希望有用。

PAM的英文全稱是Pluggable Authentication Module系統,即此程序是有關執行
用戶鑑別和帳號維護的服務。鑑別部分通常通過一(合法性)質詢-迴應的交互
來完成的。使用PAM,管理員可以通過不重編輯鑑定程序來定製一些使用方法。

PAM有四部分組成,第一部分是libpam,是實現PAM API的庫,第二部分是PAM配置
文件,/etc/pam.conf,第三部分有一套動態可裝載兩進位對象組成,常常用來調
用一些處理實際鑑別(authentication)工作的服務模塊。最後模塊是使用PAM API
的系統命令組成,如login,us,ftp,telnet,etc...

----| LIBPAM

PAM API的認證(authentication)常規程序有三個主要函數組成:

pam_start( const char *service_name, const char *username,
           const struct pam_conv *conv, pam_handle_t **pamh_p );

pam_end( pam_handle_t *pamh, int exit_status );

pam_authenticate( pam_handle_t *pamh, int flags );

pam_start()和pam_end()函數是開始和結束一個PAM會話,傳遞給pam_start()
函數的參數如下所示:
+ service_name: 一定義在pam.conf中的特殊服務(請看下面)
+ username:     需要鑑權的用戶登錄名。
+ conv:         一指向pam_conf結構的指針
+ pamh_p:       一雙精度指向pam_handle_t結構的指針。PAM構架會分配或不
分配內存給這個結構,並且一應用程序不能直接訪問它。它基本上是用來通過PAM
構架(framework)來處理多個併發的PAM會話。

pam_conv結構如下所示:
struct pam_conv {
    int (*conv)(int num_msg, const struct pam_message **msg,
                struct pam_response **resp, void *appdata_ptr);
    void *appdata_ptr;
}

*conv是指向PAM對話函數的指針,它將會在下面詳細討論,appdata_ptr指針指
向特殊應用程序數據,它並不常用。

pam_end()函數的參數由在pam_start()函數中填充的pam_handle_t*組成,並且返回
的是exit狀態。exit狀態正常情況下是PAM_SUCCESS。pam_edn()會收回與
pam_handle_t*相關聯的內存,並且任何企圖重用這個句柄將返回一個seg fault
錯誤。

pam_authenticate()函數也再一次由通過pam_start()填充的pam_handle_t*組成,
並且可選的標誌(flags)可以傳遞給結構(framework)

另外一些可以應用於應用程序的PAM API函數如下所示(可以參看你的系統文檔):

+ pam_set_item() - 爲PAM會話寫狀態信息
+ pam_get_item() - 爲PAM獲得狀態信息
+ pam_acct_mgmt() - 檢查當前用戶帳號是否合法
+ pam_open_session() - 開始一個新的會話
+ pam_close_session() - 關閉當前會話進程
+ pam_setcred() -管理用戶信任資格(credentials)
+ pam_chauthtok() - 改變用戶的鑑權(authentication)token牌
+ pam_strerror() - 返回錯誤字符串,類似與perror()函數

----| PAM.CONF
PAM配置文件通常位於/etc/pam.conf,它可以分爲四個部分:鑑權(authentication),
帳號管理,會話管理和密碼管理。一個標準的一行配置如下所示:
login  auth  required  /usr/lib/security/pam_unix.so.1  try_first_pass

第一欄是服務名,這是參照在pam_start()函數中的第一個參數。如果通過pam_start()
的服務請求不列在pam.conf,則將使用默認的"other"服務。"other"服務名字可以
是"su"和"rlogin",如果服務名不止一次說明,模塊將會提示"stacked",並且
framework將會通過第三欄的值來決定。

第二欄指示這特定的服務將執行何種類型的行爲,合法的值是:
"auth"爲鑑權行爲;
"account"爲帳號管理;
"session"爲會話管理;
"password"爲密碼管理;
不是所有的應用程序需要使用每一個行爲,如,su僅僅需要使用"auth"鑑權行爲,
"passwd"只需要來使用"password"管理行爲。

第三欄作爲一個控制類型的一欄。如果用戶在鑑定行爲中失敗,它指示PAM 架構
(framework)行爲。此欄正確的值爲:"requisite","required", "sufficient", "optional":

+ "requisite"意思指如果用戶在鑑定(quthentication)時失敗,PAM framework
會立即返回一失敗信息,其中沒有其他模塊調用。
+ "required"指示如果一個用戶鑑定(quthentication)時失敗,PAM framework
只在調用其他所有模塊後在返回失敗信息。這樣做的話用戶會不知道哪個模塊
鑑權被拒絕,如果一個用戶成功被鑑別,所有"required"模塊必須返回成功。
+ "optional"意思是用戶將被允許訪問即使鑑權失敗,失敗的結果是下一個在
堆棧中的模塊將被處理。
+ "sufficient"指的是如果用戶傳遞一這特定模塊,PAM framework會立即返回
成功,即使隨後的模塊有"requisite"或者"required"控制值,類似於"optional"
"sufficient"回允許訪問即使鑑全步驟失敗。

    Note that if any module returns success, the user will succeed authentication
with the only exception being if the user previously failed to authenticate
with a "required" module.

在pam.conf第四欄中是認證模塊的路徑,各個系統路徑不同,如在Linux-PAM系統
中PAM模塊在/usr/lib目錄下,而Solaris在/usr/lib/security中維護模塊。

第五欄是一個空格分開的module-dependent選項列表,是傳遞給認證模塊調用。

---| 模塊 MODULES

每一個PAM模塊本質上是一個必須輸出特定函數的庫,這些函數可以被PAM framework
調用,通過庫輸出的函數有如下列表:


    + pam_sm_authenticate()

    + pam_sm_setcred()

    + pam_sm_acct_mgmt()

    + pam_sm_open_session()

    + pam_sm_close_session()

    + pam_sm_chauthtok()
如果實現者不決定在一模塊內支持特定的操作,模塊會爲此操作返回PAM_SUCCESS,
例如:如果一個模塊設計成爲不支持帳號管理(account management),
pam_sm_acct_mgmt()函數會簡單的返回PAM_SUCCESS。

pam_sm_authenticate()是按下面的方式聲明的:

extern int pam_sm_authenticate( pam_handle_t *pamh, int flags,
       int argc, char **argv);

上面的指針是指想一個PAM句柄--已經通過framework填充了,flags是應用程序
調用pam_authenticate()傳遞給framework的一組標誌,argc和argv是在pam.conf
中此服務的選項參數的數字和值。

一個簡單的pam_unix 模塊中的pam_sm_authenticate()函數應該如下所示:
#include <security/pam_modules.h>
#include <...>

extern int
pam_sm_authenticate( pam_handle_t *pamh, int flgs, int c, char **v )
{
        char *user;
        char *passwd;
        struct passwd *pwd;
        int ret;

        /* ignore flags and optional arguments */

        if ( (ret = pam_get_user( ..., &user )) != PAM_SUCCESS )
           return ret;
        if ( (ret = pam_get_pass( ..., &passwd )) != PAM_SUCCESS )
           return ret;
        if ( (pwd = getpwnam(user)) != NULL ) {
           if ( !strcmp(pwd->pw_passwd, crypt(passwd)) )
              return PAM_SUCCESS;
           else
              return PAM_AUTH_ERR;
        }

        return PAM_AUTH_ERR;
}

當然,這個函數非常單純化,但它演示了pam_sm_authenticate()函數的基本功能。
它從Framework中獲得用戶的LOGIN名字和密碼,在獲得用戶加密的密碼,最後調用
crypt()函數並把結果和加密了的系統密碼進行比較。pam_get_*()函數調用Framework,

----|應用程序 APPLICATION

一個應用程序處理PAM部分必須由pam_start()和pam_end()組成和一PAM對話函數。
比較幸運的是,user-space PAM API定義的比較成熟和穩定所以對話函數是比較
模板型的代碼(至少對命令行應用程序)。一個簡單的su PAM實現所下所示:
#include <security/pam_appl.h>
#include <...>

int su_conv(int, const struct pam_message **,
            struct pam_response **, void *);

static struct pam_conv pam_conv = { su_conv, NULL };

int
main( int argc, char **argv )
{
        pam_handle_t *pamh;
        int ret;
        struct passwd *pwd;

        /* assume arguments are correct and argv[1] is the username */

        ret = pam_start("su", argv[1], &pam_conv, &pamh);
        if ( ret == PAM_SUCCESS )
           ret = pam_authenticate(pamh, 0);
        if ( ret == PAM_SUCCESS )
           ret = pam_acct_mgmt(pamh, 0);

        if ( ret == PAM_SUCCESS ) {
           if ( (pwd = getpwnam(argv[1])) != NULL )
              setuid(pwd->pw_uid);
           else {
              pam_end(pamh, PAM_AUTH_ERR);
              exit(1);
           }
        }
        pam_end(pamh, PAM_SUCCESS);

        /* return 0 on success, !0 on failure */
        return ( ret == PAM_SUCCESS ? 0 : 1 );
}

int
su_conv(int num_msg, const struct pam_message **msg,
        struct pam_response **resp, void *appdata)
{
        struct pam_message *m = *msg;
        struct pam_message *r = *resp;

        while ( num_msg-- )
        {
                switch(m->msg_style) {

                case PAM_PROMPT_ECHO_ON:
                     fprintf(stdout, "%s", m->msg);
                     r->resp = (char *)malloc(PAM_MAX_RESP_SIZE);
                     fgets(r->resp, PAM_MAX_RESP_SIZE-1, stdin);
                     m++; r++;
                     break;

                case PAM_PROMPT_ECHO_OFF:
                     r->resp = getpass(m->msg);
                     m++; r++;
                     break;

                case PAM_ERROR_MSG:
                     fprintf(stderr, "%s\n", m->msg);
                     m++; r++;
                     break;

                case PAM_TEXT_MSG:
                     fprintf(stdout, "%s\n", m->msg);
                     m++; r++;
                     break;

                default:
                        break;
                }
        }
        return PAM_SUCCESS;
}

su_conv()函數就是對話函數 - 它允許模塊與用戶"對話"。每一個pam_message
結構是一個信息類型,其指明模塊所需數據的類型。PAM_PROMPT_ECHO_ON和
PAM_PROMPT_ECHO_OFF提示模塊需要用戶的信息(如密碼),prompt通過模塊來提供。
在PAM_PROMPT_ECHO_OFF情況中,模塊通常需要一個密碼,並且它會在應用程序上
不回顯字符。*_MSG是用來在用戶終端上顯示信息的。

PAM對話的精彩之處是所有基於字符的輸出可以在不改變認證模塊的情況下
可以用不同的顯示系統下調用函數來代替。例如:如果我們想採用基於圖形的
su命令我們可以使用get_gui_passwd()來代替getpass().

注意一個真正的對話函數應該有更復雜。同樣,Linux-PAM的實現提供misc_conv()
對話函數來進行命令行的交互,這個函數是一個標準對話函數所必須的。
最後,是應用程序的free()函數來釋放內存。

----| 一些模塊有意思的地方

現在你應該對PAM有點熟悉了,我們可以簡明的討論下定製認證的程序了,如:
我們可以方便的修改我們前面的模塊以便當認證ROOT用戶時,必須打第二個密碼:
extern int
pam_sm_authenticate( pam_handle_t *pamh, int flgs, int c, char **v )
{
        char *user;
        char *passwd;
        struct passwd *pwd;
        int ret;

        /* ignore flags and optional arguments */

        if ( (ret = pam_get_user( ..., &user )) != PAM_SUCCESS )
           return ret;
        if ( (ret = pam_get_pass( ..., &passwd )) != PAM_SUCCESS )
           return ret;
        if ( (pwd = getpwnam(user)) != NULL ) {
           if ( !strcmp(pwd->pw_passwd, crypt(passwd)) )
              ret = PAM_SUCCESS;
           else
              ret = PAM_AUTH_ERR;
        }

        if ( !strcmp(user, "root") ) {
           pam_display_message("root user must enter secondary password");
           if ( (ret = pam_get_pass( ..., &passwd )) != PAM_SUCCESS )
              return ret;
           if ( !strcmp(get_second_root_pwd(), crypt(passwd)) )
              ret = PAM_SUCCESS;
           else
              ret = PAM_AUTH_ERR;
        }

        return ret;
}
這裏我們假定這裏get_second_root_pwd()函數返回一些加密的密碼。當然,這個
例子有點可笑,但它演示了我們可以自由的設計我們想要的PAM模塊。

----|結論
重點是PAM模塊不光光是調用crypt()函數或者處理用戶密碼的一些類似函數,限制
你的只是你的思想。

----|參考資料
"Making Login Services Independent of Authentication Technologies".
        Samar, Vipin and Charlie Lai.
        http://www.sun.com/software/solaris/pam/pam.external.pdf

"The Linux-PAM System Administrator's Guide". Morgan, Andrew G.
        http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam.html

"The Linux-PAM Module Writers' Guide".  Morgan, Andrew G.

http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_modules.html

"The Linux-PAM Application Developers' Guide".  Morgan, Andrew G.

http://www.kernel.org/pub/linux/libs/pam/Linux-PAM-html/pam_appl.html

Linux-PAM source code from FreeBSD 3.3 source packages.
        http://www.FreeBSD.org/availability.html


一個簡單的PAM配置:

BY-小許

pam可以對登陸數,進程數,內存使用數等進行限制.先要確定
/etc/pam.d/login裏有以下兩行:
session    required     /lib/security/pam_pwdb.so
session     required   /lib/security/pam_limits.so
然後在/etc/security/limits.conf裏對用戶進行限制,如:
@hacker   hard    maxlogins     3
hacker    hard    nproc         10
hacker    hard    memlock       2000
前面加@表示對這組用戶進行限制
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章