Chinaunix首页 | 论坛 | 博客
  • 博客访问: 152585
  • 博文数量: 51
  • 博客积分: 1545
  • 博客等级: 上尉
  • 技术积分: 600
  • 用 户 组: 普通用户
  • 注册时间: 2006-08-31 10:20
文章分类

全部博文(51)

文章存档

2011年(3)

2010年(48)

我的朋友

分类:

2010-01-20 10:56:10

学习为验证和安全服务编写 PAM(可插拔验证模块)服务模块,并研究示例模块。

目录
简介

在本系列的前三篇文章(第 1 部分第 2 部分第 3 部分)中,我们介绍了基于密码的用户验证的基础知识,并重点讨论 PAM(可插拔验证模块)的作用。我们阐述为应用程序(称为 PAM 使用者)提供验证功能的 PAM API,并演示了如何编写 PAM 会话函数。

在第 4 部分(也是最后一篇)中,我们将介绍 PAM 服务模块并演示如何编写它们的示例程序。

服务模块

服务模块 是一个共享库,它提供了以下一项或多项验证和安全服务:

  • 验证:这些服务模块用于验证对帐户或服务的访问,并设置用户凭证。
  • 帐户管理:这些服务模块确定正在访问的帐户的有效性。例如,帐户管理模块可以检查密码或帐户过期时间,或强制设置访问时间或帐户锁定限制。
  • 会话管理:这些服务模块负责设置和去除以前验证的用户或服务的 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_itempam_set_item 函数,如以下代码示例所示。还应指出 PAM 使用者可以使用这些函数与服务模块通信。

#include 

int pam_get_item(const pam_handle_t *pamh, int item_type,
void **item);

int pam_set_item(pam_handle_t *pamh, int item_type,
const void *item);

 

pam_set_item 函数允许服务模块更新句柄 pamh 指定的 PAM 事务信息。item_type 指定的信息类型可以是 pam_set_item 手册页中指定的十二种项目类型之一。项目类型示例包括 PAM_AUTHTOKPAM_CONVPAM_USERPAM_USER_PROMPT。想要将 PAM 信息设置成的值是由 item 指定的。

类似地,可以通过调用 pam_get_item 访问 PAM 事务的信息。在这种情况下,指定类型的 PAM 信息的指针被放到 item 中。

服务模块通过使用 pam_get_datapam_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 相关的函数 openlogcloselogsetlogmask 不能 在服务模块中使用,因为它们干扰应用程序的设置。

服务模块示例

既然描述了服务模块及其必须执行的工作,下面将查看一个模块。我们编写的服务模块提供了以下机制:特定组中的指定用户被拒绝访问。此服务模块适用的情况示例为 web hosting 公司:允许客户通过 ftpsftp 连接,但禁止使用登录 shell。通过使用此模块并在禁止组中指出客户名称,可以强制执行此访问策略。

此帐户访问策略类型适用于成功验证的用户,因此其特色是帐户管理。PAM 感知的应用程序调用 pam_acct_mgmt 来执行此任务,因此示例模块实现了 pam_sm_acct_mgmt,其原型如下所示:

#include 
#include

int pam_sm_acct_mgmt (pam_handle_t *pamh, int flags, int argc,
const char **argv);

 

pamh 引用 pam_start 返回的 PAM 句柄,flags 包含应用程序传递到此模块的任何标志,argcargv 包含 pam.conf 中指定的模块选项编号和相应的选项列表。

下面是示例模块的源码。

1 #include 
2 #include
3 #include
4 #include
5 #include
6 #include

7 int pam_sm_acct_mgmt (pam_handle_t *ph, int flags, int argc, char **argv)
8 {
9 char *user = NULL;
10 char *host = NULL;
11 char *service = NULL;
12 char *denied_group = "";
13 char group_buf[8192];
14 struct group grp;
15 struct pam_conv *conversation;
16 struct pam_message msg;
17 struct pam_message *msgp = &msg;
18 struct pam_response *resp = NULL;
19 int i;
20 int err;
21 int no_warn = 0;
22 int debug = 0;
23 int ret_val;

24 for (i = 0; i < argc; i++) {
25 if (strcasecmp (argv[i], "nowarn") == 0)
26 no_warn = 1;
27 else if (strcasecmp (argv[i], "debug") == 0)
28 debug = 1;
29 else if (strncmp (argv[i], "group=", 6) == 0)
30 denied_group = &argv[i][6];
31 }

32 if (flags & PAM_SILENT)
33 no_warn = 1;

34 pam_get_user (ph, &user, NULL);
35 pam_get_item (ph, PAM_SERVICE, (void **)&service);
36 pam_get_item (ph, PAM_RHOST, (void **)&host);

37 if (user == NULL) {
38 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: user not set", service);
39 ret_val = PAM_USER_UNKNOWN;
40 goto out;
41 }

42 if (host == NULL)
43 host = "unknown";

44 if (getgrnam_r (denied_group, &grp, group_buf, sizeof (group_buf)) == NULL) {
45 syslog (LOG_AUTH | LOG_NOTICE, "%s: denied_group: group \"%s\" not defined",
46 service, denied_group);
47 ret_val = PAM_SYSTEM_ERR;
48 goto out;
49 }

50 if (grp.gr_mem[0] == '\0') {
51 if (debug)
52 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: group \"%s\" is empty: "
53 "all users allowed", service, grp.gr_name);
54 ret_val = PAM_IGNORE;
55 goto out;
56 }

57 for (; grp.gr_mem[0]; grp.gr_mem++) {
58 if (strcmp (grp.gr_mem[0], user) == 0) {
59 msg.msg_style = PAM_ERROR_MSG;
60 msg.msg = "Access denied: you are not on the access list for this host.";

61 pam_get_item (ph, PAM_CONV, (void **)&conversation);
62 if ((no_warn == 0) && (conversation != NULL)) {
63 err = conversation->conv (1, &msgp, &resp, conversation->appdata_ptr);
64 if (debug && err != PAM_SUCCESS) {
65 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: conversation returned %s",
66 service, pam_strerror (ph, err));
67 }

68 if (resp != NULL) {
69 if (resp->resp)
70 free (resp->resp);
71 free (resp);
72 }
73 }

74 syslog (LOG_AUTH | LOG_NOTICE, "%s: denied_group: Connection for %s "
75 "not allowed from %s", service, user, host);
76 ret_val = PAM_PERM_DENIED;
77 goto out;
78 }
79 }

80 if (debug)
81 syslog (LOG_AUTH | LOG_DEBUG, "%s: denied_group: user %s is not a member of "
82 "group %s. Access granted.", service, user, grp.gr_name);
83
84 ret_val = PAM_SUCCESS;

85 out:
86 return (ret_val);
87 }

 

仔细研究下这个 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 -
root@ultra20# chown root:root /home/rich/pam_service_module.so

 

测试完服务模块并准备部署时,通常将共享对象置于 /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
Connection closed by 192.168.0.2

 

修改拒绝用户组(比如说修改为 root)将允许 rich 用户登录,如下所示。

rich@sunblade1000# ssh ultra20
Last login: Sun Dec 2 16:43:05 2007 from sunblade1000
Sun Microsystems Inc. SunOS 5.11 snv_70 October 2007
rich@ultra20#

 

将用户 rich 从名为 staff 的组成员列表中删除,会产生相同的效果。

结束语

在本文中,我们介绍了 PAM 服务模块的概念及其必须实现的功能(也就是我们对服务模块的期望)。首先讲到服务模块必须实现以下一个或多个函数,具体取决于服务模块的目的:pam_sm_authenticatepam_sm_acct_mgmtpam_sm_setcredpam_sm_open_sessionpam_sm_close_sessionpam_sm_chauthtok。还简要描述了服务模块和 PAM 使用者与服务模块用来彼此通信的两个函数。

然后描述了服务模块必须返回给调用者的值类型,它指示请求成功、被忽略还是产生错误(包括失败)。

然后讨论了期望的服务模块日志记录类型,以及什么时候不记录特定消息,并展示了实现以下策略的示例服务模块:拒绝访问指定组中的指定成员。

最后,展示了如何构建和安装服务模块。

阅读(1281) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~