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]