Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1283757
  • 博文数量: 554
  • 博客积分: 10425
  • 博客等级: 上将
  • 技术积分: 7555
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-09 09:49
文章分类

全部博文(554)

文章存档

2012年(1)

2011年(1)

2009年(8)

2008年(544)

分类:

2008-04-07 18:57:19

简介

本系列的第一篇文章中,我们介绍了使用口令进行用户验证的基础知识。我们对 身份验证授权这两个术语进行了定义,讨论了基于文件的本地密码存储和加密算法,并介绍了如何使用Solaris操作系统提供的API读取和加密口令。最后,我们展示了一个示例程序,它向调用者询问密码并与登录密码进行比较。

第二篇文章首先对可插拔验证模块( Pluggable Authentication Modules,PAM)进行了简单介绍,然后描述了PAM框架的各个组成部分。我们讨论了PAM服务模块、PAM 配置文件、 /etc/pam.conf,并描述了如何将服务模块连接起来。然后,我们介绍了PAM API中的一些重要函数,最后对系列文章第一部分中编写的密码比较程序进行了重写,使它具有PAM感知功能。

在这篇文章中,我们将介绍会话函数并简要查看PAM API提供的其他一些函数。

会话函数

顾名思义, 会话函数的作用就是处理与用户之间的会话。就是说,可以向用户、服务或设备显示消息,并从中收集输入内容。会话可采用多种形式,例如,文本终端设备中常见的”Login:”提示、目前最为流行的GUI登录管理器,甚至还有指纹读取器。

当使用PAM进行身份验证的应用程序(称为 PAM使用者)调用 pam_start函数(上期文章介绍了该函数)发起PAM会话时,将对会话函数进行注册。会话函数由PAM服务模块调用,其原型如下所示:

int conv_func (int

num_msg, struct pam_message **
msg,

struct pam_response **
resp, void
*app_data);

其中:

  • num_msg参数包含发送给函数的消息数量(必须在0和 PAM_MAX_NUM_MSG之间,并包括首末值)
  • msg指针指向保存显示给用户的消息的缓冲区(例如,密码提示)
  • resp指针指向保存来自用户的消息的缓冲区(例如输入的密码)
  • app_data指针指向的缓冲区包含特定于应用程序的数据

PAM负责分配和释放 msg使用的内存,而 resp使用的内存则由应用程序分配,并由服务模块释放。

我们编写的会话函数不应考虑PAM与客户进行通信的方式。相反,会话函数应与用户交换消息直至操作完成。同样,来自PAM 的消息在显示时不应被修改(服务模块负责对自身信息进行本地化)。对单独的消息没有格式限制,可以包含若干行、空白或控制符。

消息使用 pam_message结构保存,其成员如下:

struct pam_message {

int msg_style;
char *msg;
};

msg成员指实际的消息。 msg_style表示消息的类型,并且它的值可以是下面四个值的其中一个:

  • PAM_PROMPT_ECHO_OFF:提示用户,禁止回传响应。
  • PAM_PROMPT_ECHO_ON:提示用户,可以回传响应。
  • PAM_ERROR_MSG:输出错误消息。
  • PAM_TEXT_INFO:输出普通信息消息。

同样,验证模块的响应保存在 pam_response结构中,其成员如下所示:

struct pam_response {

char *resp;
int resp_retcode;
};

resp成员包含实际的响应,而 resp_retcode包含返回代码。后者并不经常使用,因此应将其设置为0。如果会话函数返回错误,必须将响应指针设置为NULL。

成员函数还有另外一项职责:必须去掉 PAM_PROMPT_ECHO_OFFPAM_PROMPT_ECHO_ON消息中的所有终止换行符,并在需要时向 PAM_ERROR_MSGPAM_TEXT_INFO消息添加新的换行符。

示例会话函数

我们已经讨论了会话函数以及其作用,现在来查看一个示例程序。源文件中提供了两个函数。一个是帮助器函数 free_resp,用于在发生错误时释放响应,第二个函数就是会话函数。

源文件中的前几行包含了我们需要的各种头文件:

1 #include 

2 #include
3 #include
4 #include
5 #include
6 #include
7 #include
8 #include

下面是 free_resp的源代码:

 9 static void free_resp (int num_msg, struct pam_response *resp)

10 {
11 int i;
12 struct pam_response *r;
13 if (resp == NULL)
14 return;
15 r = resp;
16 for (i = 0; i < num_msg; i++, r++) {
17 if (r->resp) {
18 bzero (r->resp, strlen (r->resp));
19 free (r->resp);
20 r->resp = NULL;
21 }
22 }
23 free (resp);
24 }

让我们研究一下这个由16行组成的函数。

13-14:如果 resp指针为NULL,则不执行任何操作,然后返回。

16-22:循环遍历每条消息。如果消息不为NULL,将其内存清零然后释放它。之所以在释放它之前清空内存,是因为它可能包含敏感数据,例如密码。

23:最后,释放第一个响应。

下面展示了会话函数的源代码。这些代码对本系列前一篇文章中的函数进行了改进。

25 int check_conv (int num_msg, struct pam_message **msg,

26 struct pam_response **resp, void *app_data)
27 {
28 int i;
29 struct pam_message *m;
30 struct pam_response *r;
31 char *ct_passwd;
32 m = *msg;
33 if ((num_msg <= 0) || (num_msg >= PAM_MAX_NUM_MSG)) {
34 fprintf (stderr, "Invalid number of messages\n");
35 *resp = NULL;
36 return (PAM_CONV_ERR);
37 }
38 if ((*resp = r = calloc (num_msg, sizeof (struct pam_response))) == NULL)
39 return (PAM_BUF_ERR);
40 for (i = 0; i < num_msg; i++, m++, r++) {
41 if (m->msg == NULL) {
42 fprintf (stderr, "Message %d: %d/NULL\n", i, m->msg_style);
43 goto err;
44 }
45 if (m->msg[strlen (m->msg)] == '\n')
46 m->msg[strlen (m->msg)] = '\0';
47 r->resp = NULL;
48 r->resp_retcode = 0;
49 switch (m->msg_style) {
50 case PAM_PROMPT_ECHO_OFF:
51 ct_passwd = getpassphrase (m->msg);
52 r->resp = strdup (ct_passwd);
53 break;
54 case PAM_PROMPT_ECHO_ON:
55 printf ("%s", m->msg);
56 break;
57 case PAM_ERROR_MSG:
58 fprintf (stderr, "%s\n", m->msg);
59 break;
60 case PAM_TEXT_INFO:
61 printf ("%s\n", m->msg);
62 break;
63 }
64 }
65 return (PAM_SUCCESS);
66 err:
67 free_resp (i, r);
68 *resp = NULL;
69 return (PAM_CONV_ERR);
70 }

让我们进一步查看一下这个共46行组成的函数。

33-37:验证提供的消息数是否有效(即是否在0和 PAM_MAX_NUM_MSG之间)。

38-39:为响应分配缓冲区(如果有的话)。

41-44:对于传递的所有消息,如果消息指针为NULL,则标记一个错误。

45-46:设置最后面的换行(如果有的话):如果文本为提示,则删除换行符。如果文本为消息,则在显示文本时添加换行。

47-48:初始化响应结构。

49-63:如果消息类型为 PAM_PROMPT_ECHO_OFF,调用 getpassphrase以显示保存在 m->msg中的提示并从用户处收集密码,并且不需要回传。如果消息类型为 PAM_PROMPT_ECHO_ON,则输出 m->msg中保存的提示即可(注意,真正的会话函数在执行这些操作后不会读取用户响应,但是我们在示例中忽略了这一点)。如果消息类型为 PAM_ERROR_MSGPAM_TEXT_INFO,则输出保存在 m->msg中的消息,后跟一个换行。前一种情况将把消息输出给 stderr,而后一种情况将把消息输出给 stdout

65:我们已经成功处理了所有的消息,因此返回success消息。

66-69:出现一个错误,因此清除并返回failure消息。

要使输出与系列第1部分中的输出相同,需将第51行修改为下面的内容,然后再按照第2部分编译示例程序:

ct_passwd = getpassphrase ("Enter password ");


其他PAM API函数

我们已经了解了会话函数,现在来简单了解一下PAM API提供的其他一些函数:

  • pam_acct_mgmt
  • pam_open_session
  • pam_close_session
  • pam_setcred
  • pam_set_item
  • pam_get_item
pam_acct_mgmt函数

pam_acct_mgmt函数将执行帐户验证过程,其原型如下所示:

#include 


int pam_acct_mgmt (pam_handle_t *
pamh, int
flags);


该函数将执行帐户有效性检查(例如,密码和帐户是否到期以及访问时间限制),它通常在完成身份验证(方法是调用 pam_authenticate)之后执行调用。

pam_open_sessionpam_close_session函数

启动或终止会话的PAM使用者应该调用这两个函数之一。

#include 


int pam_open_session (pam_handle_t *
pamh, int
flags);

int pam_close_session (pam_handle_t *
pamh, int
flags);

使用 pam_authenticatepam_acct_mgmt对用户进行成功验证之后,程序创建新会话时应该会调用 pam_open_session。这将通知会话模块打开新的会话。相反,关闭会话时应该调用 pam_close_session,以通知会话模块关闭会话。

pam_setcred函数

pam_setcred函数可以修改验证服务的用户凭证。

#include 


int pam_setcred (pam_handle_t *
pamh, int
flags);

当用户通过验证并打开了一个会话后,需要使用 pam_setcred函数建立、修改或删除用户凭证。

pam_set_itempam_get_item函数

PAM使用者和服务模块可以使用 pam_set_itempam_get_item处理PAM信息。

#include 


int pam_set_item (pam_handle_t *
pamh, int
item_type,

const void *
item);

int pam_get_item (const pam_handle_t *
pamh, int
item_type,

void **
item);

应用程序和服务模块可以使用 pam_set_item更新PAM信息。 item_type参数表示所更新信息的类型,并且可以表示多个类型。类型示例包括PAM服务名、用户名、tty名和用户验证标记。在 pam_set_item手册页, pam_set_item(3PAM)中可以找到完整的类型列表。

可以使用 pam_get_item访问每种消息类型的值。

因空间有限,我们无法使用这些函数展示示例,但是感兴趣的读者可以通过在 中搜索相应的函数名了解它们的用法。

结束语

在这篇文章中,我们介绍了PAM会话函数的概念以及它们的作用(即会话函数的功能)。我们介绍了如何使用会话函数处理消息,并描述了各种不同的消息类型。

随后,我们展示了一个非常普通的示例会话函数,它对系列第2部分中的函数进行了改进。

最好,我们简单介绍了PAM API提供的其他一些函数: pam_acct_mgmtpam_open_sessionpam_close_sessionpam_setcredpam_set_itempam_get_item

本系列下一篇文章(也是最后一篇)中,我们将研究如何编写PAM服务模块。

致谢

衷心感谢Glenn Brunette审阅本文。以上文章转自于 : http://developers.sun.com.cn/

 

 

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