2.3函数实现机制
Linux中传统的UNIX验证方式中,crypt()函数是一个非常重要的函数,它是GNU标准库里面的函数,默认它是基于数据加密标准(Data Encryption Standard)算法,但是GNU也对这个函数进行了扩展,也可以使用MD5等其他的加密方式来进行加密。该函数原型为 char *crypt(const char *key,const char *salt);其中key就是要被加密的密码字符串,salt这个值是用来对密码字符串进行加密,不同的salt值就会产生不同的密码。从/etc/shadow中可以看出其密码字段的格式就是$id$salt$encrypted-string,id取值为1的时候采用MD5的方式进行加密,取值为2a则是采用Blowfish的方式进行加密,取值为5采用SHA-256的方式进行加密,取值为6时采用SHA-512的方式进行加密。salt就是上面所说的salt,encrypted-string就是加密后的密文。下面介绍两种加密方式中salt的取值方式。
在DES的加密方式中,salt是从[a-z A-Z 0-9 ./]中选择的一个包含两个字符的字符串,这个字符串用来以4096种不同的方法对这个算法进行初始化。这个算法使用密码中前8个字符的低7位来生成一个密钥。这个密钥用来反复对一个常量字符串(包含0)进行转换。返回值是一个包含13个可打印字符的字符串,用来表示加密后的密码。但是这种方式加密的密码在当前的计算机发展能力下是可以通过并行计算机对整个密钥空间遍历搜索得到的。
在MD5的加密方式中,salt是一个以3个字符"$1$"开始的字符串,后面是前8个字(可以以"$"结束)并输出34个字节的内容。具体来说,这些字节是$1$[string]$[encrypted-string],其中[string]代表salt中"$1$"后面的8个字符,后面是22个从[a-zA-Z0-9./]集合中选出的字符。
通过分析passwd.c得知具体salt的生成是通过crypt_make_salt来生成的,其函数定义为char *crypt_make_salt (const char *meth, void *arg);通过传递的meth这个参数来确定是采用哪种加密方式,从而生成不同的salt长度传递给gensalt()这个函数,由该函数随机产生一组固定长度的字符串。salt就是随机产生的一组字符串,然后经过相应的加密算法来对密码进行加密生成密文。
在PAM验证机制中提供了各种接口函数给用户使用,从而调用模块来实现验证,查看openpam源码中pam_acct_mgmt()接口函数的调用过程,其调用层次图如下所示:
图2-4 pam_acct_mgmt调用层次图
PAM的API通过服务分发函数openpam_dispatch将服务定向到具体的服务模块,并检查当前服务模块函数是否为空,如果不为空则表示该模块正在被使用中,需要停止操作,这样可以防止同一模块重复调用,接着会遍历整个服务模块栈,然后调用服务模块的具体函数。 可以看出PAM只为具体的服务模块提供一个接口,并在头文件中定义好一个供函数调用的钩子函数int(*pam_func_t)(structpam_handle *,int,int,constchar**);从而调用到具体的服务模块函数。
pam_handle_t *pamh是贯穿整个pam的重要结构,是pam机制各种信息的从入口点,它描述了整个PAM过程,该结构定义于libpam/pam_private.h中,具体定义如下:
代码2-1 pam_handle结构体
主要字段含义如下表所示:
表2-1 pam_handle中字段含义
类型
字段
含义
char
*authtok;
认证口令
unsigned
caller_is
用于用户记录
struct pam_conv
*pam_conversation
应用程序设置对话函数
char
*oldauthtok
旧的用户口令
char
*prompt
用户提示符,用于获得信息
char
*service_name
服务名称
char
*user
用户名
char
*rthost
远程主机名
char
*ruser
远程用户名
char
*tty
终端名
char
*authtok_type
PAM密码认证类型
struct service
handlers
服务类型的处理函数
const char
*mod_name
服务模块名称
其中读取的服务名等信息中可以读取到相应的服务名,从服务名找到相关配置文件,读取的信息就存放在这个service结构体中,service结构体中存储了与服务相关的PAM接口处理函数集以及模块使用计数,该结构体也位于libpam/pam_private.h中,具体定义如下:
代码2-2 service结构体代码
各字段含义如下表所示:
表2-2 service中字段含义
类型
字段
含义
struct loaded_module
*modules
用于装载动态模块
int
modules_allocated
已分配的模块数
int
moudles_used
已使用的模块数
int
handlers_loaded
已装载的服务模块个数
struct handlers
conf
从配置文件读取的模块处理
struct handlers
other
从默认的配置文件即other的模块处理
对于loaded_module结构,其代表的是动态装载的服务模块,其定义在libpam/pam_private.h中,具体定义如下:
代码2-3 loaded_module结构体定义
其具体字段含义如下表:
表2-3 loaded_module中字段含义
类型
字段
含义
char
name
装载模块名称
int
type
装载模块类型
void
dl_handle
处理函数
对于handlers这个结构体,是用于存放读取到的接口函数指针,该结构体就包含了PAM中服务模块的函数接口指针,其定义在libpam/pam_private.h中,具体定义如下:
代码2-4 handlers结构体定义
该结构体中具体字段含义如下表:
表2-4 handlers中字段含义
类型
字段
含义
struct handler
*authenticate
认证用户
struct handler
*setcred
修改信任参数
struct handler
*acct_mgmt
检查帐号是否有效
struct handler
*open_session
建立会话
struct handler
*close_session
关闭会话
struct handler
*chauthtok
修改密码等
服务接口函数指针的具体指向是用handler这个结构体来描述的,该结构体中描述了每个PAM服务接口函数指针、参数以及返回值等信息,其定义在libpam/pam_private.h中,具体定义如下:
代码2-5 handler结构体定义
该结构体中具体字段含义如下表:
表2-5 handler中字段含义
类型
字段
含义
int
handler_type
处理函数类型
int
(*func)
处理函数
int
actions
返回类型
int
argc
传递给func的参数
int
argv
传递给func的参数
struct handler
next
下一个处理函数
char
*mod_name
模块名称
对于上述结构体,其关系图如下:
图2-5 PAM相关结构体之间的关系图
PAM提供了这些数据结构,获得基本信息,找到相应配置文件并接着逐层调用找到应用服务模块的函数调用入口或者直接是通过这些基本信息,通过service这个结构体中提供的类型为loaded_module的字段module将验证模块挂载在整个服务模块函数中,这些服务模块函数都存放在modules中。