四、第二层:应用接口层


应用接口层位于 PAM 结构的中间部分,它向上为应用程序屏蔽了用户鉴别等过程的具体细节,向下调用模块层中的具体模块所提供的特定服务。由图1可以看出,它主要由 PAM API 和配置文件两部分组成,下面将逐一介绍。


PAM API 可以分为两类,一类是用于调用下层特定模块的接口,这类接口与底层的模块相对应:


1. 鉴别类接口:pam_authenticate()用于鉴别用户,pam_setcred()用于修改用户的秘密信息。


2. 帐号类接口:pam_acct_mgmt()检查受鉴别的用户所持帐户是否有权登陆系统,以及该帐户是否已过期等。


3. 会话类接口:包括用于会话管理和记帐的 pam_open_session()和 pam_close_session()函数。


4. 口令类接口:包括用于修改用户口令的 pam_chauthtok()。




第二类接口通常并不与底层模块一一对应,它们的作用是对底层模块提供支持以及实现应用程序与模块之间的通信等。具体如下:


1. 管理性接口


每组 PAM 事务从 pam_start()开始,结束于 pam_end()函数。接口 pam_get_item()和 pam_set_item()用来读写与 PAM 事务有关的状态信息。同时,能够用 pam_str()输出 PAM 接口的出错信息。


2. 应用程序与模块间的通讯接口


在应用程序初始化期间,某些诸如用户名之类的数据可以通过 pam_start()将其存放在PAM接口层中,以备将来底层模块使用。另外,底层模块还可以使用 pam_putenv()向应用程序传递特定的环境变量,然后应用程序利用 pam_getenv() 和 pam_getenvlist() 读取这些变量。


3. 用户与模块间的通讯接口


pam_start()函数可以通过会话式的回调函数,让底层模块通过它们读写模块相关的鉴别信息,比如以应用程序所规定的方式提示用户输入口令。


4. 模块间通讯接口


尽管各模块是独立的,但是他们仍然能够通过 pam_get_item()和 pam_set_item()接口共享某些与鉴别会话有关的公用信息,诸如用户名、服务名、口令等。此外,这些API还可以用于在调用 pam_start()之后,让应用程序修改状态信息。


5. 读写模块状态信息的接口


接口 pam_get_data()和 pam_set_data()用以按照PAM句柄要求访问和更新特定模块的信息。此外,还可以在这些模块后附加一个清除数据函数,以便当调用 pam_end()时清除现场。


由于 PAM 模块随需加载,所以各模块始化任务在第一次调用时完成。如果某些模块的清除任务必须在鉴别会话结束时完成,则它们应该使用 pam_set_data()规定清除函数,这些执行清除任务的函数将在应用程序调用 pam_end()接口时被调用。





五、配置文件


我们注意到,配置文件也放在了在应用接口层中,它与 PAM API 配合使用,从而达到了在应用中灵活插入所需鉴别模块的目的。它的作用主要是为应用选定具体的鉴别模块,模块间的组合以及规定模块的行为。下面是一个示例配置文件:


095033991.gif


图2.示例配置文件




我们可以看到,配置文件有许多登记项(每行对应一个登记项)组成,每一行又分为五列(每列对应一栏),详细解释如下:



第一栏,service表示使用PAM的应用程序,比如login、passwd、rlogin等。这一栏中的OTHER表示所有没在该文件中显式列出的应用。也就是说,如果所有程序具有相同的需求,整个配置文件只需要一行即可,并且该行的第一栏为OTHER。本例中,因为所有应用程序使用相同的会话模块,所以实际上可以用单行,即


" OTHER auth required pam_unix_auth.so"


来代替文件中的这些行:


"login session required pam_unix_session.so


ftp session required pam_unix_session.so


telnet session required pam_unix_session.so"。




第二栏,module_type指明程序所用PAM底层模块的类型:auth表示鉴别类模块;account表示帐户类模块;session表示会话类模块;password表示口令类模块。注意,每行只能指定一种类型模块,如果程序需要多种模块的话,可在多行中分别规定。




第三栏,control_flag规定如何处理模块的成功和失败情况。单个应用程序可以调用多种底层模块,这通常称为"堆叠",对应于某程序的按照配置文件中出现顺序执行的所有模块成为"堆",堆中的各模块的地位与出错时的处理由control_flag栏的取值决定,它的五种可能的取值分别为 required、Requisite、sufficient或_optional,现介绍如下:


required--它表示该模块的成功是用户通过鉴别的必要条件,换句话说,只有当对应于应用程序的所有带required标记的模块全部成功后,该程序才能通过鉴别。同时,如果任何带required标记的模块出现了错误,PAM并不立刻将错误消息返回给应用程序,而是在所有模块都调用完毕后才将错误消息返回调用它的程序。


Requisite--它与required相仿,只有带此标记的模块返回成功后,用户才能通过鉴别,不同之处在于其一旦失败就不再执行堆中后面的其它模块,并且鉴别过程到此结束。


optional--它表示即便该模块失败,用户仍能通过鉴别。在PAM体系中,带有该标记的模块失败后将继续处理下一模块。


sufficient--它表示该模块取得成功是用户通过鉴别的充分条件,也就是说只要标记为sufficient的模块一旦成功,那么PAM便立即向应用程序返回成功而不必尝试任何其他模块。当标记为sufficient的模块失败时,sufficient模块当做optional对待。




第四栏,module_path指出PAM模块的位置。




第五栏,options用于向特定模块传递相关的选项,然后由模块分析解释这些任选项。比如使用此栏打开模块调试,或向某模块传递诸如超时值之类的参数等。另外,它还用于支持下文所述的口令映射技术。


如果任一栏出现错误,或某模块没有找到,那么所在行被忽略并将其作为严重错误进行记录。


本例中,login程序使用UNIX口令模块进行鉴别,而ftp程序却使用S/Key模块进行鉴别。如我们想改变ftp程序的鉴别方法,比如也用UNIX口令模块进行鉴别,那么我们不必改动源程序,只需将配置文件中的


ftp auth required pam_skey_auth.so debug


改为


ftp auth required pam_unix_auth.so debug


这样,当用户使用ftp时,将用传统的UNIX口令鉴别方式来验证其身份。由此可见,在PAM体制下为应用程序改变鉴别机制是一件轻松的事情。另外,PAM体制的堆叠功能还使得应用程序能够支持多种鉴别机制,如下例中的login程序在配置文件中先后出现了三项与鉴别有关的登记项:


095138938.gif




图3.示例配置文件


当login程序执行时先用pam_unix.so模块即传统的UNIX口令方式鉴别用户,然后再调用pam_kerb.so模块即 Kerberos对用户进行鉴别,最后用pam_rsa.so模块即RSA方式鉴别用户。在按上述顺序鉴别用户的过程中,如果pam_unix.so模块鉴别失败,它将继续调用下面的模块进行鉴别而非立刻向login程序返回错误消息;pam_kerb.so模块也按同样方式处理,直到顺序处理完最后一个 pam_rsa.so模块后,PAM才将前面出现的错误信息返回给login程序。对于该配置,即使pam_rsa.so模块顺利通过,只要 pam_unix.so模块和pam_kerb.so模块中有一个出现错误,用户就不能通过鉴别;相反,即使pam_rsa.so模块失败,只要 pam_unix.so模块和pam_kerb.so模块都通过了,用户也能通过鉴别。





六、口令映射


在同一个机器上使用多个鉴别机制,尤其是一个应用程序集成多种鉴别机制可能导致用户需要记忆多个口令,这会让用户觉得很不舒服。虽然可以让所有机制使用相同的口令来获取易用性,但是这将削弱系统的安全性--如果其中任何一个机制的口令泄露了,则所有机制都会受到牵累。此外,不同的鉴别机制在口令长度、容许的字符、更新间隔、有效期等方面可能具有他们特有的要求,这些要求也是为多鉴别机制使用同一个口令必须考虑的一个问题。


PAM为我们提供了这样一种解决方案,它不排除为所有鉴别机制共用一个口令,同时允许通过口令映射技术让每个机制使用不同的口令。该方案用用户的" 主口令"加密其他的"副口令",并且将这些经过加密的副口令存放在一个用户能访问的地方。主口令一旦经过验证,鉴别模块就能用它解密那些加密的副口令从而获得相应口令,然后将所需口令传递给鉴别模块。这称为"口令映射"。如果口令映射出现错误,或如果映射不存在,那么各鉴别模块应该提示用户输入口令。为支持口令映射,主口令应保存在PAM第二层并且在需要时由其提供给堆叠的各个鉴别模块。同时,口令要在pam_authenticate函数返回之前清除。为了保障口令映射的安全,主口令必须足够强壮,可以考虑使其的长度更长、组成口令字符的类型多样化并使用混合类型的字符组成口令等有效措施。


口令如何加密及其存储完全取决于具体的实现:它能够将加密的副口令(也称作"映射口令")存储在可靠或不可靠的地方,诸如智能卡、本地文件或目录服务。当然,如果加密的口令保存在一个不可靠的允许公共访问的地方,会留下受到字典***的隐患。




为实现口令映射,所有鉴别模块应支持以下四个映射选项∶


1. use_first_pass∶它表示当该模块执行时不提示用户输入口令,而将该模块之前的提示用户输入的主口令作为它们的公共口令进行验证。如果用户没能通过主口令的鉴别,则该模块不提示用户输入口令。此选项一般说来在系统管理员想强制用同一个口令通过多模块时使用。


2. try_first_pass∶除了如果主口令不正确,将提示用户输入口令之外,它的用法与use_first_pass相同。


3. use_mapped_pass∶它表示使用口令映射技术得到此模块的有效口令。也就是说,该模块执行时不提示用户输入口令,而是用映射口令即用主口令解密得到的该模块的副口令作为本模块的口令输入进行验证。如果在此之前用户没能通过主口令的鉴别,则该模块也不会提示输入口令。


4. try_mapped_pass∶除了如果主口令不正确,它将提示用户输入口令之外,该项与use_mapped_pass用法相同。


当口令更换后,PAM会保存所有新旧口令,并且使有关模块能够访问到它们。其他模块能够使用此信息更新加密的口令而不必强制用户再次输入口令。


现以下面的配置文件为例讲解口令映射:



095203739.gif

图4. 示例配置文件


在这里login程序集成了三种鉴别方式:传统UNIX口令鉴别、Kerberos和RSA鉴别,但通常情况下用户仅输入一次口令便能通过鉴别了。当程序调用pam_unix.so模块时,PAM提示用户输入他们的UNIX口令,然后由pam_kerb.so模块对用户输入的UNIX口令进行鉴别。继而调用pam_kerb.so模块,由于该模块的选项为use_mapped_pass,它将利用口令映射机制进行认证,也就是说,如果UNIX口令鉴别通过的话,就将其作为pam_kerb.so模块的主口令来解密其对应的映射口令从而进行Kerberos鉴别。如果pam_unix.so模块所需口令没能通过验证,则无法进行口令映射,那么PAM将直接调用下一鉴别模块而不提示用户输入其Kerberos口令。最后一个模块的选项为 use_first_pass,所以pam_rsa.so模块将使用前边输入的主口令来鉴别用户,如果口令错误也不提示用户输入RSA口令。所以,只要第一次输入的口令是正确的,并且映射口令存在,则一个口令便足以通过鉴别。





七、结束语


Linux-PAM是一种使用灵活功能强大的用户鉴别机制,本文对它的组成结构以及各部分之间的关系进行了相应的分析,希望对读者理解PAM的机制有所帮助。