接前一篇文章:PAM从入门到精通(十九)
本文参考:
《The Linux-PAM Application Developers' Guide》
先再来重温一下PAM系统架构:
更加形象的形式:
七、PAM-API各函数源码详解
前边的文章讲解了各PAM-API函数以及总体流程,但是也只是从接口层面介绍的,并没有深入到代码层面。从本篇文章开始,将对于各个接口函数从源码级进行讲解,以使大家不但知其然,还要知其所以然。
1. pam_start函数
老规矩,先从pam_start函数开始。先再来看一下pam_start()的说明。
概述:
PAM事务初始化。
函数声明:
#include <security/pam_appl.h>
int pam_start ( service_name , user , pam_conversation , pamh );
const char * service_name ;
const char * user ;
const struct pam_conv * pam_conversation ;
pam_handle_t ** pamh ;
其余函数相关细节说明随着源码一起讲解。
pam_start函数在Linux-PAM-1.5.2源码根目录的libpam/pam_start.c中,代码如下:
int pam_start (
const char *service_name,
const char *user,
const struct pam_conv *pam_conversation,
pam_handle_t **pamh)
{
return _pam_start_internal(service_name, user, pam_conversation,
NULL, pamh);
}
由代码可见,pam_start()只是一层简单的封装,实际的工作完全交给了_pam_start_internal函数。该函数在同文件(libpam/pam_start.c)中,代码如下:
static int _pam_start_internal (
const char *service_name,
const char *user,
const struct pam_conv *pam_conversation,
const char *confdir,
pam_handle_t **pamh)
{
D(("called pam_start: [%s] [%s] [%p] [%p]"
,service_name, user, pam_conversation, pamh));
if (pamh == NULL) {
pam_syslog(NULL, LOG_CRIT,
"pam_start: invalid argument: pamh == NULL");
return (PAM_SYSTEM_ERR);
}
if (service_name == NULL) {
pam_syslog(NULL, LOG_CRIT,
"pam_start: invalid argument: service == NULL");
return (PAM_SYSTEM_ERR);
}
if (pam_conversation == NULL) {
pam_syslog(NULL, LOG_CRIT,
"pam_start: invalid argument: conv == NULL");
return (PAM_SYSTEM_ERR);
}
if ((*pamh = calloc(1, sizeof(**pamh))) == NULL) {
pam_syslog(NULL, LOG_CRIT, "pam_start: calloc failed for *pamh");
return (PAM_BUF_ERR);
}
/* All service names should be files below /etc/pam.d and nothing
else. Forbid paths. */
if (strrchr(service_name, '/') != NULL)
service_name = strrchr(service_name, '/') + 1;
/* Mark the caller as the application - permission to do certain
things is limited to a module or an application */
__PAM_TO_APP(*pamh);
if (((*pamh)->service_name = _pam_strdup(service_name)) == NULL) {
pam_syslog(*pamh, LOG_CRIT,
"pam_start: _pam_strdup failed for service name");
_pam_drop(*pamh);
return (PAM_BUF_ERR);
} else {
char *tmp;
for (tmp=(*pamh)->service_name; *tmp; ++tmp)
*tmp = tolower(*tmp); /* require lower case */
}
if (user) {
if (((*pamh)->user = _pam_strdup(user)) == NULL) {
pam_syslog(*pamh, LOG_CRIT,
"pam_start: _pam_strdup failed for user");
_pam_drop((*pamh)->service_name);
_pam_drop(*pamh);
return (PAM_BUF_ERR);
}
} else
(*pamh)->user = NULL;
if (confdir) {
if (((*pamh)->confdir = _pam_strdup(confdir)) == NULL) {
pam_syslog(*pamh, LOG_CRIT,
"pam_start: _pam_strdup failed for confdir");
_pam_drop((*pamh)->service_name);
_pam_drop((*pamh)->user);
_pam_drop(*pamh);
return (PAM_BUF_ERR);
}
} else
(*pamh)->confdir = NULL;
(*pamh)->tty = NULL;
(*pamh)->prompt = NULL; /* prompt for pam_get_user() */
(*pamh)->ruser = NULL;
(*pamh)->rhost = NULL;
(*pamh)->authtok = NULL;
(*pamh)->oldauthtok = NULL;
(*pamh)->fail_delay.delay_fn_ptr = NULL;
(*pamh)->former.choice = PAM_NOT_STACKED;
(*pamh)->former.substates = NULL;
#ifdef HAVE_LIBAUDIT
(*pamh)->audit_state = 0;
#endif
(*pamh)->xdisplay = NULL;
(*pamh)->authtok_type = NULL;
(*pamh)->authtok_verified = 0;
memset (&((*pamh)->xauth), 0, sizeof ((*pamh)->xauth));
if (((*pamh)->pam_conversation = (struct pam_conv *)
malloc(sizeof(struct pam_conv))) == NULL) {
pam_syslog(*pamh, LOG_CRIT, "pam_start: malloc failed for pam_conv");
_pam_drop((*pamh)->service_name);
_pam_drop((*pamh)->user);
_pam_drop((*pamh)->confdir);
_pam_drop(*pamh);
return (PAM_BUF_ERR);
} else {
memcpy((*pamh)->pam_conversation, pam_conversation,
sizeof(struct pam_conv));
}
(*pamh)->data = NULL;
if ( _pam_make_env(*pamh) != PAM_SUCCESS ) {
pam_syslog(*pamh,LOG_ERR,"pam_start: failed to initialize environment");
_pam_drop((*pamh)->pam_conversation);
_pam_drop((*pamh)->service_name);
_pam_drop((*pamh)->user);
_pam_drop((*pamh)->confdir);
_pam_drop(*pamh);
return PAM_ABORT;
}
_pam_reset_timer(*pamh); /* initialize timer support */
_pam_start_handlers(*pamh); /* cannot fail */
/* According to the SunOS man pages, loading modules and resolving
* symbols happens on the first call from the application. */
if ( _pam_init_handlers(*pamh) != PAM_SUCCESS ) {
pam_syslog(*pamh, LOG_ERR, "pam_start: failed to initialize handlers");
_pam_drop_env(*pamh); /* purge the environment */
_pam_drop((*pamh)->pam_conversation);
_pam_drop((*pamh)->service_name);
_pam_drop((*pamh)->user);
_pam_drop((*pamh)->confdir);
_pam_drop(*pamh);
return PAM_ABORT;
}
D(("exiting pam_start successfully"));
return PAM_SUCCESS;
}
_pam_start_internal函数一开始先给出调试信息,并且进行参数检查。代码片段如下:
D(("called pam_start: [%s] [%s] [%p] [%p]"
,service_name, user, pam_conversation, pamh));
if (pamh == NULL) {
pam_syslog(NULL, LOG_CRIT,
"pam_start: invalid argument: pamh == NULL");
return (PAM_SYSTEM_ERR);
}
if (service_name == NULL) {
pam_syslog(NULL, LOG_CRIT,
"pam_start: invalid argument: service == NULL");
return (PAM_SYSTEM_ERR);
}
if (pam_conversation == NULL) {
pam_syslog(NULL, LOG_CRIT,
"pam_start: invalid argument: conv == NULL");
return (PAM_SYSTEM_ERR);
}
service_name是由pam_start函数传下来的参数,其作用是:指定要应用的服务的名称,并将作为PAM_SEVICE项存储在新上下文中。服务的策略将从文件/etc/pam.d/service_name中读取,如果该文件不存在,则从/etc/pam.conf中读取。如:passwd(/etc/pam.d/passwd)、useradd(/etc/pam.d/useradd)等。
pamh也是由pam_start函数传下来的参数,其内容是一个句柄,它包含对PAM函数的连续调用的PAM上下文。在错误情况下,pamh的内容未定义。pam_handle_t是一个盲结构,应用程序不应试图直接探测其信息。而是应该通过PAM库提供的函数pam_set_item和pam_get_item。PAM句柄不能同时用于多个身份验证,只要以前没有对其调用pam_end函数。
之前一直没有给出pamh的类型pam_handle_t的定义,在这里必须要弄清楚了。其定义在libpam/pam_private.h中,代码如下:
struct pam_handle {
char *authtok;
unsigned caller_is;
struct pam_conv *pam_conversation;
char *oldauthtok;
char *prompt; /* for use by pam_get_user() */
char *service_name;
char *user;
char *rhost;
char *ruser;
char *tty;
char *xdisplay;
char *authtok_type; /* PAM_AUTHTOK_TYPE */
struct pam_data *data;
struct pam_environ *env; /* structure to maintain environment list */
struct _pam_fail_delay fail_delay; /* helper function for easy delays */
struct pam_xauth_data xauth; /* auth info for X display */
struct service handlers;
struct _pam_former_state former; /* library state - support for
event driven applications */
const char *mod_name; /* Name of the module currently executed */
int mod_argc; /* Number of module arguments */
char **mod_argv; /* module arguments */
int choice; /* Which function we call from the module */
#ifdef HAVE_LIBAUDIT
int audit_state; /* keep track of reported audit messages */
#endif
int authtok_verified;
char *confdir;
};
libpam/include/security/_pam_types.h中:
/* This is a blind structure; users aren't allowed to see inside a
* pam_handle_t, so we don't define struct pam_handle here. This is
* defined in a file private to the PAM library. (i.e., it's private
* to PAM service modules, too!) */
typedef struct pam_handle pam_handle_t;
pam_conversation也是由pam_start函数传下来的参数,其指向描述要使用的会话函数的结构pam_conv。应用程序必须为加载的模块与应用程序之间的直接通信提供此功能。
struct pam_conv的定义在libpam/include/security/_pam_types.h中,代码如下:
/* The actual conversation structure itself */
struct pam_conv {
int (*conv)(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr);
void *appdata_ptr;
};
做完第一步参数检查的工作后,下边正式开始进行实际功能实现。欲知后事如何,且看下回分解。