当前位置:Linux教程 - Linux资讯 - PAM 的应用开发和内部实现源码分析

PAM 的应用开发和内部实现源码分析

  1 引言  身份认证是操作系统安全的重要机制之一,系统通过认证机制核查用户的身份证明,并作为用户进入系统的判定条件,是防止恶意用户进入系统的第一道门槛。近年来认证理论和技术得到了迅速发展,产生了各种认证机制,如口令机制,RSA, DCE, kerberos认证体制,S/Key和基于智能卡的身份认证等。然而,当系统中引入新的认证机制时,一些系统入口登录服务如login, rlogin和telnet等应用程序就必须改写以适应新的认证机制。为了解决这个问题,1995年Sun公司的Vipin Samar和 Charlie Lai提出了PAM(Pluggable Authentication Modules),并将其应用在Solaris系统上。PAM框架将应用程序与具体的认证机制分离,使得系统改变认证机制时,不再需要修改采用认证机制的应用程序,而只要由管理员配置应用程序的认证服务模块,极大地提高了认证机制的通用性与灵活性。    现在大多数操作系统都采用PAM实现身份认证,有Linux系统的Linux-PAM和FreeBSD5.x采用的OpenPAM(FreeBSD 4.x采用的是Linux-PAM)等。它们的实现原理一样,只有实现细节不同而已。下面从PAM的应用开发开始介绍。    2 PAM的应用开发  2.1 PAM框架概览  PAM即可插拔认证模块。它提供了对所有服务进行认证的中央机制,适用于login,远程登录(telnet,rlogin,fsh,FTP,点对点协议(PPP)),su等应用程序中。系统管理员通过PAM配置文件来制定不同应用程序的不同认证策略;应用程序开发者通过在服务程序中使用PAM API(pam_xxxx( ))来实现对认证方法的调用;而PAM服务模块的开发者则利用PAM SPI来编写模块(主要是引出一些函数pam_sm_xxxx( )供PAM接口库调用),将不同的认证机制加入到系统中;PAM接口库(libpam)则读取配置文件,将应用程序和相应的PAM服务模块联系起来。PAM框架结构如图所示。       其中,pamh是一个pam_handle类型的结构,它是一个非常重要的处理句柄,是PAM与应用程序通信的唯一数据结构,也是调用PAM接口库API的唯一句柄。pam_handle数据结构将在下面的源代码分析一节的介绍。    另外,如上图所示的服务模块分auth(认证管理)、account(账号管理)、session(会话管理)、passwd(口令管理)四种类型,各个类型模块的作用以及配置文件的四个组成部分模块类型、控制标志、模块路径、模块参数等在很多讲PAM的配置管理的文章里都有介绍,这里就不再赘述了。    2.2 在应用程序中使用PAM认证  每个使用PAM认证的应用程序都以pam_start开始,pam_end结束。PAM还提供了pam_get_item和pam_set_item共享有关认证会话的某些公共信息,例如用户名,服务名,密码和会话函数。应用程序在调用了pam_start ()后也能够用这些APIs来改变状态信息。实际做认证工作的API函数有六个(以下将这六个函数简称为认证API):    认证管理--包括pam_authenticate ()函数认证用户,pam_setcred ()设置,刷新,或销毁用户证书。  账号管理--包括pam_acc_mgmt ()检查认证的用户是否可以访问他们的账户,该函数可以实现口令有效期,访问时间限制等。  会话管理--包括pam_open_session ()和pam_close_session ()函数用来管理会话和记账。例如,系统可以存储会话的全部时间。  口令管理--包括pam_chauthok ()函数用来改变密码。  下面看一个简单的login模拟程序:    /* 使用PAM所必需的两个头文件*/  #include   #include     void main(int argc, char *argv[], char **renvp)  {  /* 初始化,并提供一个回调函数 */  if ((pam_start("login", user_name, &pam_conv, &pamh)) != PAM_SUCCESS)  exit(1);    /* 设置一些关于认证用户信息的参数 */  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);  }    /* 通过了密码认证之后再调用帐号管理API,检查用户帐号是否已经过期 */  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事务的结束 */  ……  }    从上面程序中,我们可以了解到使用PAM认证的一般流程,同时也可以看出PAM API使得使用认证的应用程序不仅不用关心底层使用的服务模块,而且编写起来简洁明了得多。    有关开发使用PAM的应用程序更加详细完整的阐述请参考The Linux-PAM Application Developers' Guide。    2.3 怎样开发PAM服务模块  首先在编写的服务模块的源程序里要包含下列头文件:    #include     PAM的服务模块是一个一个的动态链接库文件(也可以是静态库),PAM接口库通过dlopen来装载这些库。假设源程序名为pam_module-name.c,则需要用下列命令将其编译成动态链接库:    gcc -fPIC -c pam_module-name.c  ld -x --shared -o pam_module-name.so pam_module-name.o    选项-fPIC是指位置无关代码(Position Independent Code),这类代码支持大偏移。使用--shared选项将目标代码放进共享目标库中。    四种类型的模块各自要实现的函数如下表所示:       当然同一个服务模块可以同时属于多种类型,只要这些类型模块要实现的函数都实现了就可以,比如PAM自带的经典口令认证机制模块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./* --- 认证管理函数的实现--- */  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./* --- 账号管理函数的实现 --- */  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./* --- 口令管理函数的实现 --- */  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./* --- 会话管理函数的实现 --- */  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./* 模块定义结束 */  41./* 静态模块数据 */  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行包含静态模块的一些原型申明,第37-39行实现了四种模块类型的函数,因为这只是一个简单的拒绝服务的模块,所以这些函数只是简单地返回认证错或系统错等PAM错误。最后几行定义了该程序被编译成静态模块所需的一个模块数据结构。    因此,PAM SPI使得服务模块的开发也相当简单和专一,因为服务模块不再需要考虑和应用程序的交互,只要将自己采用的算法实现好就可以了。    模块源程序可用的flags参数值和返回值的定义这里不作全面介绍,有兴趣者请参考The Linux-PAM Module Writers' Guide。    3 PAM接口库源代码分析  上面我们介绍了怎样使用PAM和怎样开发PAM服务模块,要想对PAM的内部机制有个透彻的理解,还需要进一步分析PAM接口库的代码。下面基于Linux 的pam-0.75-40.src.rpm包所得的源
[1] [2] 下一页 

(出处:http://www.sheup.com)


上一页 [1] [2]