上一篇转贴了一些关于PAM结构的一些内容,关于应用程序如何调用PAM 的API实现身份认证和会话操作的内容不是这篇内容所关心的,就不在赘述.对话函数作为应用程序和PAM 模块信息交互的"中介",其作用非常的重要.因此下面准备小谈一下对话函数.
我本人也是新手,自己写一个对话函数肯定是要漏洞百出,那下面就转贴一段代码和大家一块分析下好了
这里的链接是代码出处.点击打开链接
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <security/pam_appl.h>
int
converse(int n, const struct pam_message **msg,
struct pam_response **resp, void *data)
{
struct pam_response *aresp;
char buf[PAM_MAX_RESP_SIZE];
int i;
data = data;
if (n <= 0 || n > PAM_MAX_NUM_MSG)
return (PAM_CONV_ERR);
if ((aresp = calloc(n, sizeof *aresp)) == NULL)
return (PAM_BUF_ERR);
for (i = 0; i < n; ++i) {
aresp[i].resp_retcode = 0;
aresp[i].resp = NULL;
switch (msg[i]->msg_style) {
case PAM_PROMPT_ECHO_OFF:
aresp[i].resp = strdup(getpass(msg[i]->msg));
if (aresp[i].resp == NULL)
goto fail;
break;
case PAM_PROMPT_ECHO_ON:
fputs(msg[i]->msg, stderr);
if (fgets(buf, sizeof buf, stdin) == NULL)
goto fail;
aresp[i].resp = strdup(buf);
if (aresp[i].resp == NULL)
goto fail;
break;
case PAM_ERROR_MSG:
fputs(msg[i]->msg, stderr);
if (strlen(msg[i]->msg) > 0 &&
msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
fputc('\n', stderr);
break;
case PAM_TEXT_INFO:
fputs(msg[i]->msg, stdout);
if (strlen(msg[i]->msg) > 0 &&
msg[i]->msg[strlen(msg[i]->msg) - 1] != '\n')
fputc('\n', stdout);
break;
default:
goto fail;
}
}
*resp = aresp;
return (PAM_SUCCESS);
fail:
for (i = 0; i < n; ++i) {
if (aresp[i].resp != NULL) {
memset(aresp[i].resp, 0, strlen(aresp[i].resp));
free(aresp[i].resp);
}
}
memset(aresp, 0, n * sizeof *aresp);
*resp = NULL;
return (PAM_CONV_ERR);
}
注释
这段C代码中首先是包含了一个叫做pam_appl.h的头文件,这个文件在 /usr/include/security/目录下,正如它的名字,这个头文件中声明的是PAM API函数,这些API是供Application使用的.
接下来代码实现了一个叫做对话函数的函数converse,这个函数原型是 int converse(int n, const struct pam_message **msg, struct pam_response **resp, void **data);这个函数的参数意义上一篇已经说过了,
可能还记得pam_start(.., const struct pam_conv *pam_conversation,..)这个函数吧,pam_conv结构的第一个成员就是一个对话函数指针,不错,应用程序里如果想使用PAM进行认证之类的操作就需要自己实现一个类似本示例程序中的对话函数,并通过pam_start()的pam_conversation这个参数把应用程序实现的对话函数告诉PAM库.
下面看下对话函数的内容吧.
示例的对话函数首先检查了第一个参数n是否合法,n表示了msg中请求的条数,如果合法,接下来就循环处理每一条"请求"了。
我们可以看到在循环体内有一个switch语句,通过这个流程应用程序把msg中的每一个请求的具体类型进行了区分,
若请求类型(pam_message.msg_style)为 PAM_PROMPT_ECHO_OFF 对话函数调用了一个getpass(const char *prompt)(可能在有界面的应用程序中会调用getguipass**之类的函数吧),意思是提示用输入一些内容,这些内容呢会被当做密码输入似的不会显示(或者以'*'显示)在屏幕上.这类函数通常允许有一个常字符串类型的入参,就是我们看到的输出到屏幕上的提示信息,如"请输入密码" "Password"...
若请求类型为 PAM_PROMPT_ECHO_ON 对话函数将提示信息直接输送到了标准错误输出设备上,通常这个标准错误输出设备与stdout是一致的,然后直接调用fgets来获得用户的输入,很明显用户输入的信息将显示在屏幕上.
若请求类型为 PAM_ERROR_MSG 则对话函数直接把msg[i]->msg中的内容放到了标准错误设备上,并进行了换行处理
若请求类型为 PAM_TEXT_INFO 则对话函数直接把msg[i]->msg中的内容放到了标准输出设备上,并进行换行处理
其他情况就是错误的情况了. 现在应该了解msg_style的每一个值的效果了吧,上面代码就是一个对话函数的大致流程了,复杂一些的对话函数的结构和这个事例是一致的.
其实我最想研究的是这个代码中内存开辟的一些细节,因为对话函数有一个特点,就是它内部产生的内存开辟需要由调用者进行释放,因此我们一起看下对话函数中各个环节的内存开辟情况.
事例代码中第一处开辟空间是 calloc(n, sizeof *aresp),这是为msg的响应开辟空间,如果开辟失败了返回PAM_BUF_ERR,接下来在PAM_PROMPT_ECHO_OFF 和PAM_PROMPT_ECHO_ON这两分支中都调用了strdup这个函数,这个函数内部要调用malloc进行内存开辟的,函数若调用失败会返回NULL.
因此如果一个对话函数如果成功返回在我们的PAM 模块中我们需要记得释放空间有*resp 和resp[i]->resp这些内存空间
若对话函数执行失败在我们的模块中不需要考虑空间的释放问题,一个好的对话函数在不能成功执行用户的请求的时候应该保证自己分配的空间会自己处理掉,但很遗憾上面的这个示例对话函数中还是有一些东西没有释放.仔细看一下我们发现这个对话函数调用了memset(aresp, 0, n*sizeof *aresp);(这个处理很好,把操作的痕迹从内存中抹去),但是很遗憾它应该在返回之前调用free(aresp)的,因为我们可以看到返回之前执行了*resp=NULL,因此如果respa不在返回前释放的话这块内存将永远释放不掉了.
可能你还是没有体会到对话函数的重要性,没关系后面会给出一个简单的事例的,也许等你看了以后会恍然大悟的...