本节介绍如何编写 PAM 服务模块。
PAM 服务模块使用 pam_get_item(3PAM) 和 pam_set_item(3PAM) 与应用程序进行通信。要相互进行通信,服务模块需要使用 pam_get_data(3PAM) 和 pam_set_data(3PAM)。如果同一项目的服务模块需要交换数据,则应建立该项目的唯一数据名称。然后,服务模块即可通过 pam_get_data() 和 pam_set_data() 函数共享此数据。
服务模块必须返回以下三类 PAM 返回码之一:
如果模块在所请求的策略中做出了明确决定,则返回 PAM_SUCCESS。
如果模块未做出策略决定,则返回 PAM_IGNORE。
如果模块参与的决定导致失败,则返回 PAM_error。error 可以是常规错误代码或特定于服务模块类型的代码。错误不能是其他服务模块类型的错误代码。有关错误代码,请参见特定的 pam_sm_module-type 手册页。
如果服务模块执行多个函数,则应将这些函数分成单独的模块。使用此方法,系统管理员可对策略配置进行更为精细的控制。
应该为任何新的服务模块提供手册页。手册页应该包括以下各项:
模块接受的参数。
模块实现的所有函数。
标志对算法的影响。
任何所需的 PAM 项。
特定于此模块的错误返回信息。
服务模块必须支持 PAM_SILENT 标志,以防止显示消息。建议使用 debug 参数将调试信息记录到 syslog 中。请将 syslog(3C) 与 LOG_AUTH 和 LOG_DEBUG 结合使用来记录调试。其他消息应发送到具有 LOG_AUTH 和相应优先级的 syslog()。决不能使用 openlog(3C)、closelog(3C) 和 setlogmask(3C),因为这些函数会干扰应用程序设置。
以下是一个 PAM 服务模块样例。此示例将查看用户是否是允许访问此服务的组的成员。如果成功,则提供者随后将授予访问权限,如果失败则记录错误消息。该示例执行以下步骤:
解析从 /etc/pam.conf 中的配置行传递到此模块的标志。
此模块可接受 nowarn 和 debug 标志以及特定的标志 group。使用 group 标志可以配置模块,允许其访问除缺省使用的组 root 以外的特定组。有关示例,请参见源代码中 DEFAULT_GROUP 的定义。例如,要允许属于组 staff 的用户访问 telnet(1),用户可以使用以下行(位于 /etc/pam.conf 中的 telnet 栈中):
telnet account required pam_members_only.so.1 group=staff
获取用户名、服务名和主机名。
用户名通过调用 pam_get_user(3PAM)(用于从 PAM 句柄中检索当前用户名)获取。如果未设置用户名,则会拒绝访问。服务名和主机名通过调用 pam_get_item(3PAM) 获取。
验证要使用的信息。
如果未设置用户名,则拒绝访问。如果未定义要使用的组,则拒绝访问。
验证当前用户是否是允许访问此主机并且授予访问权限的特殊组的成员。
如果该特殊组已定义但根本不包含任何成员,则将返回 PAM_IGNORE,指明此模块不参与任何帐户验证过程。该决策将留给栈中的其他模块。
如果用户不是特殊组的成员,则会显示一条消息,通知用户访问被拒绝。
记录消息以记录此事件。
以下示例给出了 PAM 提供者样例的源代码。
此示例的源代码也可以通过 Sun 下载中心获得。请访问 http://www.sun.com/download/products.xml?id=41912db5
/* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include <stdio.h> #include <stdlib.h> #include <grp.h> #include <string.h> #include <syslog.h> #include <libintl.h> #include <security/pam_appl.h> /* * by default, only users who are a member of group "root" are allowed access */ #define DEFAULT_GROUP "root" static char *NOMSG = "Sorry, you are not on the access list for this host - access denied."; int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char **argv) { char *user = NULL; char *host = NULL; char *service = NULL; const char *allowed_grp = DEFAULT_GROUP; char grp_buf[4096]; struct group grp; struct pam_conv *conversation; struct pam_message message; struct pam_message *pmessage = &message; struct pam_response *res = NULL; int i; int nowarn = 0; int debug = 0; /* Set flags to display warnings if in debug mode. */ for (i = 0; i < argc; i++) { if (strcasecmp(argv[i], "nowarn") == 0) nowarn = 1; else if (strcasecmp(argv[i], "debug") == 0) debug = 1; else if (strncmp(argv[i], "group=", 6) == 0) allowed_grp = &argv[i][6]; } if (flags & PAM_SILENT) nowarn = 1; /* Get user name,service name, and host name. */ (void) pam_get_user(pamh, &user, NULL); (void) pam_get_item(pamh, PAM_SERVICE, (void **) &service); (void) pam_get_item(pamh, PAM_RHOST, (void **) &host); /* Deny access if user is NULL. */ if (user == NULL) { syslog(LOG_AUTH|LOG_DEBUG, "%s: members_only: user not set", service); return (PAM_USER_UNKNOWN); } if (host == NULL) host = "unknown"; /* * Deny access if vuser group is required and user is not in vuser * group */ if (getgrnam_r(allowed_grp, &grp, grp_buf, sizeof (grp_buf)) == NULL) { syslog(LOG_NOTICE|LOG_AUTH, "%s: members_only: group \"%s\" not defined", service, allowed_grp); return (PAM_SYSTEM_ERR); } /* Ignore this module if group contains no members. */ if (grp.gr_mem[0] == 0) { if (debug) syslog(LOG_AUTH|LOG_DEBUG, "%s: members_only: group %s empty: " "all users allowed.", service, grp.gr_name); return (PAM_IGNORE); } /* Check to see if user is in group. If so, return SUCCESS. */ for (; grp.gr_mem[0]; grp.gr_mem++) { if (strcmp(grp.gr_mem[0], user) == 0) { if (debug) syslog(LOG_AUTH|LOG_DEBUG, "%s: user %s is member of group %s. " "Access allowed.", service, user, grp.gr_name); return (PAM_SUCCESS); } } /* * User is not a member of the group. * Set message style to error and specify denial message. */ message.msg_style = PAM_ERROR_MSG; message.msg = gettext(NOMSG); /* Use conversation function to display denial message to user. */ (void) pam_get_item(pamh, PAM_CONV, (void **) &conversation); if (nowarn == 0 && conversation != NULL) { int err; err = conversation->conv(1, &pmessage, &res, conversation->appdata_ptr); if (debug && err != PAM_SUCCESS) syslog(LOG_AUTH|LOG_DEBUG, "%s: members_only: conversation returned " "error %d (%s).", service, err, pam_strerror(pamh, err)); /* free response (if any) */ if (res != NULL) { if (res->resp) free(res->resp); free(res); } } /* Report denial to system log and return error to caller. */ syslog(LOG_NOTICE | LOG_AUTH, "%s: members_only: " "Connection for %s not allowed from %s", service, user, host); return (PAM_PERM_DENIED); }