分类:
2010-01-20 10:56:10
学习为验证和安全服务编写 PAM(可插拔验证模块)服务模块,并研究示例模块。
在本系列的前三篇文章(第 1 部分、第 2 部分 和 第 3 部分)中,我们介绍了基于密码的用户验证的基础知识,并重点讨论 PAM(可插拔验证模块)的作用。我们阐述为应用程序(称为 PAM 使用者)提供验证功能的 PAM API,并演示了如何编写 PAM 会话函数。
在第 4 部分(也是最后一篇)中,我们将介绍 PAM 服务模块并演示如何编写它们的示例程序。
服务模块 是一个共享库,它提供了以下一项或多项验证和安全服务:
理想情况下,PAM 服务应该在任务简单并且定义明确的服务模块中实现,因此增加配置的灵活性。然后即可根据 PAM 配置文件(/etc/pam.conf
)中相应定义的要求使用服务模块。我们在本系列的 第 2 部分 中描述了 PAM 配置文件。
在本系列的第 2 部分中我们提到 PAM 使用者调用以下一个或多个函数来执行用户验证和相关功能:
pam_authenticate
pam_acct_mgmt
pam_setcred
pam_open_session
pam_close_session
pam_chauthtok
以上每个函数都是由服务模块中拥有相同名称(但 pam_
前缀替换成 pam_sm_
)的函数实现的。因此 pam_authenticate
是由 pam_sm_authenticate
实现的,pam_sm_acct_mgmt
实现 pam_acct_mgmt
,以次类推。我们编写的服务模块必须提供以上一个或多个函数。
为了与 PAM 使用者应用程序通信,服务模块使用 pam_get_item
和 pam_set_item
函数,如以下代码示例所示。还应指出 PAM 使用者可以使用这些函数与服务模块通信。
#include |
pam_set_item
函数允许服务模块更新句柄 pamh 指定的 PAM 事务信息。item_type 指定的信息类型可以是 pam_set_item
手册页中指定的十二种项目类型之一。项目类型示例包括 PAM_AUTHTOK
、PAM_CONV
、PAM_USER
和 PAM_USER_PROMPT
。想要将 PAM 信息设置成的值是由 item 指定的。
类似地,可以通过调用 pam_get_item
访问 PAM 事务的信息。在这种情况下,指定类型的 PAM 信息的指针被放到 item 中。
服务模块通过使用 pam_get_data
和 pam_set_data
函数,可以访问和更新特定于模块的信息。我们不深入讨论这些函数,因为我们重点研究 PAM 服务模块及其使用者之间的通信。有兴趣的读者可以参考这些函数的手册页,了解更多详细信息。
PAM 服务模块必须向其使用者提供 PAM 返回代码。返回代码必须为以下三种类型之一:
PAM_SUCCESS
。 模块做出肯定性的决定,此决定是所请求的策略的一部分。PAM_IGNORE
。 模块没有做出属于所请求的策略一部分的决定。PAM_<error>
。 模块做出否定性的决定,此决定是所请求的策略的一部分。错误代码可以是普通的 PAM 基础架构错误代码(例如,PAM_USER_UNKNOWN
是指示 PAM 句柄指定的用户未知的错误,PAM_PERM_DENIED
的意思是此模块拒绝了验证请求,等等),也可以是特定于模块的错误代码。在后一种情况下,错误代码必须是每个模块唯一的(也就是,不能被任何其他模块使用),因此应该记录在模块的手册页中。要防止显式不需要的消息,所有服务模块都必须遵照 PAM_SILENT
标志的指示。推荐使用 debug
标志,以便通过 syslog
设施将诊断调试信息记入日志。使用 syslog
记录的调试消息应该使用 LOG_AUTH
设施和 LOG_DEBUG
严重性级别。使用 syslog
记录的任何其他消息都应该使用 LOG_AUTH
设施和相应的优先级。
重要注意事项: syslog
相关的函数 openlog
、closelog
和 setlogmask
不能 在服务模块中使用,因为它们干扰应用程序的设置。
既然描述了服务模块及其必须执行的工作,下面将查看一个模块。我们编写的服务模块提供了以下机制:特定组中的指定用户被拒绝访问。此服务模块适用的情况示例为 web hosting 公司:允许客户通过 ftp
和 sftp
连接,但禁止使用登录 shell。通过使用此模块并在禁止组中指出客户名称,可以强制执行此访问策略。
此帐户访问策略类型适用于成功验证的用户,因此其特色是帐户管理。PAM 感知的应用程序调用 pam_acct_mgmt
来执行此任务,因此示例模块实现了 pam_sm_acct_mgmt
,其原型如下所示:
#include |
pamh 引用 pam_start
返回的 PAM 句柄,flags 包含应用程序传递到此模块的任何标志,argc 和 argv 包含 pam.conf
中指定的模块选项编号和相应的选项列表。
下面是示例模块的源码。
1 #include |
仔细研究下这个 80 行的函数。注意,为了便于讨论本示例,我们随意将缓冲区 group_buf
(在第 13 行定义)限制为 8K 字符。在实际的程序中,可能根据系统的最大值动态地设置此缓冲区的大小,通过调用 sysconf
确定。
1-6:
包含必要的头文件。
24-33:
解释模块选项,设置合适的调试和非警告标志。
34-36:
获得用户、服务和远程主机名称。
37-41:
如果用户未指定,则拒绝访问。
44-49:
如果指定的组未定义,则拒绝访问。
50-56:
如果指定的组没有指定成员,则允许所有用户访问。
57-79:
检查用户是不是组的成员。如果是,则拒绝访问,并且(如果没有禁用警告)调用会话函数向用户传递相应的错误消息。注意,总是向 syslog
报告此拒绝。注意,为了简便起见,我们使用 strcmp
来比较用户名。在实际的应用程序中,可能使用 strncmp
,以避免缓冲区溢出。还要注意,第 66 行中 pam_strerror
的用法。此函数返回与第二个参数相关的错误消息,其处理方式与 strerror
处理常规错误消息相同。
80-84:
如果执行到此处,则用户不是指定组的成员,因此允许访问。
85-87:
返回调用程序。
服务模块是共享对象,因此可借助 Sun 的 Studio 编译器使用以下命令来构建示例。
rich@ultra20# cc -c -Kpic -o pam_service_module.so pam_service_module.c |
(gcc
用户应该将 -Kpic
替换成 -fpic
。)
PAM 基础架构执行各种安全检查,因此共享对象必须归 root
所有,如以下示例所示。
rich@ultra20# su - |
测试完服务模块并准备部署时,通常将共享对象置于 /usr/lib/security/$ISA
中,其中 $ISA
表示目标机器的指令集。另一个可能安装此模块的位置(如果以包的格式提供模块)是 /opt/lib/security/$ISA
。
最后,必须向 pam.conf
添加新模块的一个条目,如下所示。
other account required /home/rich/pam_service_module.so group=staff debug |
一切正常的话,则组 staff
的指定成员将被拒绝访问。指定为 staff
组成员的 rich
用户尝试使用 ssh
登录时,就会失败,如以下示例所示。(使用 telnet
也会失败,但是有安全意识的人不应该使用 telnet
。)
rich@sunblade1000# ssh ultra20 |
修改拒绝用户组(比如说修改为 root
)将允许 rich
用户登录,如下所示。
rich@sunblade1000# ssh ultra20 |
将用户 rich
从名为 staff
的组成员列表中删除,会产生相同的效果。
在本文中,我们介绍了 PAM 服务模块的概念及其必须实现的功能(也就是我们对服务模块的期望)。首先讲到服务模块必须实现以下一个或多个函数,具体取决于服务模块的目的:pam_sm_authenticate
、pam_sm_acct_mgmt
、pam_sm_setcred
、pam_sm_open_session
、pam_sm_close_session
和 pam_sm_chauthtok
。还简要描述了服务模块和 PAM 使用者与服务模块用来彼此通信的两个函数。
然后描述了服务模块必须返回给调用者的值类型,它指示请求成功、被忽略还是产生错误(包括失败)。
然后讨论了期望的服务模块日志记录类型,以及什么时候不记录特定消息,并展示了实现以下策略的示例服务模块:拒绝访问指定组中的指定成员。
最后,展示了如何构建和安装服务模块。