一.PAM简介

Linux-PAM(linux可插入认证模块)是一套共享库,使本地系统管理员可以随意选择程序的认证方式.
换句话说,不用(重新编写和)重新编译一个包含PAM功能的应用程序,就可以改变它使用的认证机制.
这种方式下,就算升级本地认证机制,也不用修改程序.
PAM使用配置文件/etc/pam.conf(或/etc/pam.d/下的文件),来管理对程序的认证方式.应用程序
调用相应的配置文件,从而调用本地的认证模块.模块放置在/lib/security下,以加载动态库的形式进
行调用(dlopen(3)).
像我们使用su命令时,系统会提示你输入root用户的密码.这就是su命令通过调用PAM模块实现的.

二. PAM的配置文件介绍

1.PAM配置文件的格式

PAM配置文件有两种写法: 一种是写在/etc/pam.conf中.格式如下:
ftpd auth required pam_unix.so nullok

ftpd:表示服务名,即针对哪一个服务进行的认证配置.
required:为模块类型.PAM有四中模块类型,分别代表不同的任务类型.
pam_unix.so:为模块路径.即要调用模块的位置.
nullok:为模块参数,即传递给模块的参数.

另一种写法是,将PAM配置文件放到/etc/pam.d/目录下,使用应用程序名作为配置文件名.如:
vsftpd,login等.配置文件的格式与pam.conf类似,只是少了最左边的服务名列.如:/etc/pam.d/cups

#%PAM-1.0
auth    required        pam_stack.so service=system-auth
account required        pam_stack.so service=system-auth

2.PAM的模块类型

Linux-PAM有四种模块类型,分别代表四种不同的任务.它们是:认证管理,账号管理,会话管理和密码
管理.一个类型可能有多行,它们按顺序依次由PAM模块调用.

auth
	用来对用户的身份进行识别.如:提示用户输入密码,或判断用户是否为root等.
account
	对帐号的各项属性进行检查.如:是否允许登录,是否达到最大用户数,或是root用户是否允许在
这个终端登录等.

session
	这个模块用来定义用户登录前的,及用户退出后所要进行的操作.如:登录连接信息,用户数据的
打开与关闭,挂载文件系统等.

password
	使用用户信息来更新.如:修改用户密码.

3.PAM的控制标记

PAM使用控制标记来处理和判断各个模块的返回值.

required
	这个标记表示需要模块返回一个成功值.如果返回失败,则继续进行同类型的下一个操作,当
所有此类型的模块都执行完后.才返回失败值.

requisite
	与required相似,但是如果这个模块返回失败,则立刻向应用程序返回失败,表示此类型失败.
不再进行同类型后面的操作.

sufficient
	如果此模块返回成功,则直接向应用程序返回成功,表示此类型成功.不再进行同类型后面
的操作.如果失败,也不会影响这个类型的返回值.

optional
	使用这个标记的模块,将不进行成功与否的返回.一般返回一个PAM_IGNORE(忽略).

4.模块路径

模块路径.即要调用模块的位置. 一般保存在/lib/security/下,如: pam_unix.so
同一个模块,可以出现在不同的类型中.它在不同的类型中所执行的操作都不相同.这是由于每个模块
针对不同的模块类型,编制了不同的执行函数.

5.模块参数

模块参数,即传递给模块的参数.参数可以有多个,之间用空格分隔开,如:

password   required   pam_unix.so nullok obscure min=4 max=8 md5

三.编写PAM配置文件

1.PAM模块介绍

pam_unix.so模块:
auth类型: 提示用户输入密码,并与/etc/shadow文件相比对.匹配返回0(PAM_SUCCESS).
account类型: 检查用户的账号信息(包括是否过期等).帐号可用时,返回0.
password类型: 修改用户的密码. 将用户输入的密码,作为用户的新密码更新shadow文件

pam_cracklib.so模块:
password类型: 这个模块可以插入到一个程序的密码栈中,用于检查密码的强度.
这个模块的动作是提示用户输入密码,并与系统中的字典进行比对,检查其强度.

pam_loginuid.so模块:
session类型:用来设置已通过认证的进程的uid.以使程序通过正常的审核(audit).

pam_securetty.so模块:
auth类型: 如果用户要以root登录时,则登录的tty必须在在/etc/securetty中法之前.

pam_rootok.so模块:
auth类型: pam_rootok模块用来认证用户id是否为0.为0返回”PAM_SUCCESS”.

pam_console.so模块:
session类型: 当用户登录到终端时,改变终端文件文件的权限.在用户登出后,再将它们修改回来.

pam_permit.so模块:
auth,account,password,session类型: pam_permit模块任何时候都返回成功.

pam_env.so模块 :
auth类型: pam_env允许设置环境变量.默认下,若没有指定文件,将依据
/etc/security/pam_env.conf进行环境变量的设置.

pam_xauth.so模块:
session类型: pam_xauth用来在用户之间转发xauth-key.
如果不进行pam_xauth,当用户调用su成为另一个用户时,这个用户将不可以再访问原来用户的X显示,
因为新用户没有访问显示的key.pam_xauth解决了当会话建立时,从原始用户到目标用户转发key和用户
退出时销毁key的问题.

实验:
注销/etc/pam.d/su中的"session    optional     /lib/security/$ISA/pam_xauth.so"行
在桌面终端执行su切换到另一个用户时,执行xterm会报错,提示无法访问DISPLAY.
删除注释后,再使用su切换到另一个用户时,执行xterm,会正常打开一个xterm终端窗口.


pam_stack.so模块:
auth,account,password,session: pam_stack可以调用另一个服务.也就是多个服务可以包含到一个
设置中.当需要修改时,只修改一个文件就可以了.

pam_warn.so模块:
auth,account,password,session: pam_warn用来记录服务,终端,用户,远程用户和远程主机的信息
到系统日志.模块总是返回PAM_IGNORE,意指不希望影响到认证处理.

2.编写PAM配置文件

以root身份执行: # vi /etc/pam.d/pamtest

#提示用户输入密码
auth     required   pam_unix.so
# 验证用户账号是否可用
account  required   pam_unix.so
# 向系统日志输出一条信息
account  required   pam_warn.so

四.基于PAM机制的应用程序

1.编写C源码

#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <security/pam_modules.h>
#include <stdio.h>
/* 文件pamtest.c
此程序从命令行接收一个用户名作为参数,然后对这个用户名进行auth和account验证.
*/
// 定义一个pam_conv结构,用于与pam通信
       static struct pam_conv conv = {
           misc_conv,
           NULL
       };
// 主函数
       int main(int argc, char *argv[])
       {
           pam_handle_t *pamh=NULL;
           int retval;
           const char *user="nobody";
	   const char *s1=NULL;

           if(argc == 2)
               user = argv[1];
	   else
	       exit(1);

           if(argc > 2) {
               fprintf(stderr, "Usage: pamtest0 [username]\n");
               exit(1);
           }
	   printf("user: %s\n",user);
	   retval = 0;

//调用pamtest配置文件
           retval = pam_start("pamtest", user, &conv, &pamh);

           if (retval == PAM_SUCCESS)

//进行auth类型认证
               retval = pam_authenticate(pamh, 0);    /* is user really user? */
	   else {
//如果认证出错,pam_strerror将输出错误信息.
		   printf("pam_authenticate(): %d\n",retval);
		   s1=pam_strerror( pamh, retval);
		   printf("%s\n",s1);
		}
           if (retval == PAM_SUCCESS)

//进行account类型认证
               retval = pam_acct_mgmt(pamh, 0);       /* permitted access? */
	   else {
		   printf("pam_acct_mgmt() : %d\n",retval);
		   s1=pam_strerror( pamh, retval);
		   printf("%s\n",s1);
		}
           /* This is where we have been authorized or not. */

           if (retval == PAM_SUCCESS) {
               fprintf(stdout, "Authenticated\n");
           } else {
               fprintf(stdout, "Not Authenticated\n");
           }

           if (pam_end(pamh,retval) != PAM_SUCCESS) {     /* close Linux-PAM */
               pamh = NULL;
               fprintf(stderr, "pamtest0: failed to release authenticator\n");
               exit(1);
           }

           return ( retval == PAM_SUCCESS ? 0:1 );       /* indicate success */
       }
//END

2.编译

$ cc -o pamtest pamtest.c -lpam -lpam_misc -ldl

3.编写PAM配置文件

以root身份执行: vi /etc/pam.d/pamtest

auth  required  /lib/security/pam_unix.so
account required /lib/security/pam_unix.so

4. 修改可执行程序权限

由于pam_unix.so需要访问/etc/shadow和/etc/passwd文件,所以要给pamtest文件附上SUID权限.

# chown root.root pamtest
# chmod 111 pamtest
# ls pamtest
# ls pamtest -hl
---s--x--x 1 root root 12K 2007-07-16 01:52 pamtest

5.执行

pamtest程序通过pam_unix.so,先对用户的密码进行验证,然后对用户的账号信息进行验证.
以普通用户身份执行,输入错误的maj密码时.

maj@m2-u:01:52:09/var/tmp$ ./pamtest maj
user: maj
Password: 
pam_acct_mgmt() : 7
Authentication failure
Not Authenticated

输入正确的密码时

maj@m2-u:01:54:44/var/tmp$ ./pamtest maj
user: maj
Password: 
Authenticated

输入错误的root密码时

maj@m2-u:01:58:37/var/tmp$ ./pamtest root
user: root
Password: 
pam_acct_mgmt() : 7
Authentication failure
Not Authenticated
maj@m2-u:01:59:15/var/tmp$ 

输入正确的root密码时

maj@m2-u:01:54:50/var/tmp$ ./pamtest root
user: root
Password: 
Authenticated

maj@m2-u:01:58:37/var/tmp$


下面是Linux提供的PAM模块列表(只是其中一部分):
模块文件          模块功能描述                  相关配置文件
pam_access      提供logdaemon风格的登录控制    /etc/security/access.conf
pam_chroot      提供类似chroot命令的功能
pam_cracklib    对密码的强度进行一定的检查    库文件libcrack和字典文件
                                               /usr/lib/cracklib_dict
pam_deny        总是无条件地使认证失败
pam_env          设置或取消环境变量            /etc/security/pam_env.conf
pam_filter      对输入输出流进行过滤          filters
pam_ftp.so      对匿名ftp用户进行认证
pam_group        当用户在指定的终端上请求指定的 /etc/security/group.conf
                          服务时赋予该用户相应的组权限
pam_issue        在提示用户输入用户名之前显示  /etc/issue
                        /etc/issue文件的内容
pam_krb4        对用户密码进行Kerberos认证    相应的Kerberos库文件
pam_lastlog      在用户登录成功后显示关于      /var/log/lastlog
                         用户上次登录的信息,并维护/var/log/lastlog文件。
pam_limits      限制用户会话所能使用的系统资源 /etc/security/limits.conf
pam_listfile    根据指定的某个文件决定是否    例如/etc/ftpusers允许或禁止提供服务
pam_mail        检查用户的邮箱中是否有新邮件    /var/spool/mail/xxxx
pam_mkhomedir    为用户建立主目录                /etc/skel/
pam_motd        显示/etc/motd文件的内容        /etc/motd
pam_nologin      根据/etc/nologin文件的存在与否  /etc/nologin来决定用户认证是否成功
pam_permit      总是无条件地使认证成功
pam_pwdb        作为pam_unix_xxxx模块的一个替代。/etc/pwdb.conf
                         使用Password Database通用接口进行认证。
pam_radius      提供远程身份验证拨入用户服务(RADIUS)的认证
pam_rhosts_auth   利用文件~/.rhosts和/etc/hosts.equiv和~/.rhosts
                              /etc/hosts.equiv对用户进行认证。
pam_rootok      检查用户是否为超级用户,如果是超级用户则无条件地通过认证。
pam_securetty    提供标准的Unix securetty检查/etc/securetty
pam_time        提供基于时间的控制,比如限制/etc/security/time.conf
                        用户只能在某个时间段内才能登录
pam_unix        提供标准的Unix认证/etc/passwd和 /etc/shadow
pam_userdb      利用Berkeley DB数据库来检查    Berkeley DB用户/密码
pam_warn        利用syslog( )记录一条告警信息
pam_wheel        只允许wheel组的用户有超级用户的存取权限

转载于:http://www.turbolinux.com.cn/turbo/wiki/doku.php?id=%E7%BD%91%E7%BB%9C%E7%AE%A1%E7%90%86:pam%E9%AA%8C%E8%AF%81%E6%9C%BA%E5%88%B6