介绍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
本人水平有限,错误在所难免,尽请指教xundi@xfocus.org
原文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
前面加@表示对这组用户进行限制

|EOF|-------------------------------------------------------------------------|
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值