编写支持Pam认证的程序

# 1、相关数据结构

1
2
3
4
5
struct pam_conv {
      int (*conv)(int num_msg, const struct pam_message **msg,
             struct pam_response **resp, void *appdata_ptr);
      void *appdate_ptr;
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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
};
typedef struct pam_handle pam_handle_t;

# 2、相关API

# 2.1 库函数

库函数主要用于使二进制实现PAM机制流程

# 2.1.1 pam_start()

函数声明:

1
2
3
4
5
6
int pam_start(
    const char *service_name,
    const char *user,
    const struct pam_conv *pam_conversation,
    pam_handle_t **pamh
);
  • service_name:指定 PAM 服务名称。它对应于 PAM 配置文件中的条目(通常位于 /etc/pam.d/ 目录下)。例如,SSH 服务的配置文件通常是 /etc/pam.d/sshd,其服务名称就是 “sshd"。
  • user:指定要认证的用户名。如果在调用 pam_start 时不知道用户名,可以传递 NULL,稍后通过 pam_get_user() 获取。
  • pam_conversation:该结构定义了 PAM 与应用程序之间的对话机制。通过这个结构,PAM 可以向应用程序请求输入(如密码、确认信息等)并接收响应。
  • pamhpam_start()会分配并初始化这个结构,后续的 PAM 操作将使用这个句柄。调用者应在操作完成后通过 **pam_end()**函数释放资源。
  • 返回值:
    • PAM_SUCCESS:表示操作成功。
    • 其他返回值:表示出现错误,可以参考 PAM 手册获取错误代码的详细信息。

# 2.1.2 pam_end()

该函数用来结束一个pam会话。

函数声明:

1
int pam_end(pam_handle_t *pamh, int pam_status);
  • pamh:该结构由之前的 pam_start 函数初始化。pam_end 函数使用它来确定要结束的 PAM 会话。
  • pam_status:表示 PAM 操作的最后状态。通常传递最后一次 PAM 函数调用的返回值,或者 PAM_SUCCESS 表示成功。此状态信息有助于 PAM 模块执行任何必要的清理工作。
  • 返回值:
    • PAM_SUCCESS:表示操作成功。
    • 其他返回值:表示出现错误,可以参考 PAM 手册获取错误代码的详细信息。

# 2.1.3 pam_authenticate()

该函数是PAM(Pluggable Authentication Module)库中用于验证用户身份的关键函数之一,它根据配置的认证模块(例如密码、指纹、双因素认证等)进行用户身份验证。

函数声明:

1
int pam_authenticate(pam_handle_t *pamh, int flags);
  • pamh:该结构由之前的 pam_start() 函数初始化,pam_authenticate() 使用这个句柄来获取认证信息并与 PAM 配置进行交互。
  • flags:一个整数标志,用于控制认证行为。常用标志包括:
    • PAM_SILENT:静默模式,不生成任何输出。
    • PAM_DISALLOW_NULL_AUTHTOK:如果认证令牌(如密码)为空,则认证失败。
  • 返回值:
    • PAM_SUCCESS:表示认证成功。
    • 其他返回值:表示出现错误,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_AUTH_ERR:认证失败。
      • PAM_USER_UNKNOWN:用户未知。
      • PAM_CRED_INSUFFICIENT:凭据不足。
      • PAM_AUTHINFO_UNAVAIL:认证信息不可用。

# 2.1.4 pam_acct_mgmt()

该函数用于执行账户管理任务,例如检查用户账户是否已过期、账户是否被锁定或是否有其他限制条件。它通常在用户身份认证(pam_authenticate)之后调用,以确保用户账户状态良好并允许访问。

函数原型:

1
int pam_acct_mgmt(pam_handle_t *pamh, int flags);
  • pamh:该结构由之前的 pam_start() 函数初始化。pam_acct_mgmt() 使用这个句柄来获取账户信息并与 PAM 配置进行交互。
  • flags:这是一个整数标志,用于控制账户管理行为。常用标志包括:
    • PAM_SILENT:静默模式,不生成任何输出。
    • PAM_DISALLOW_NULL_AUTHTOK
  • 返回值:
    • PAM_SUCCESS:表示账户检查通过,没有问题。
    • 其他返回值:表示出现错误,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_ACCT_EXPIRED:账户已过期。
      • PAM_AUTH_ERR:认证失败。
      • PAM_PERM_DENIED:权限被拒绝。
      • PAM_NEW_AUTHTOK_REQD:需要更新认证令牌(例如密码过期需要更改)。

# 2.1.5 pam_get_user()

用于获取当前会话中要认证的用户名。它可以在认证过程中或账户管理阶段被调用,以便获取或提示输入用户名。

函数原型:

1
int pam_get_user(pam_handle_t *pamh, const char **user, const char *prompt);
  • pamh:该结构由 pam_start 函数初始化。pam_get_user 使用这个句柄来访问 PAM 会话的上下文。
  • user:将获取到的用户名存储在这个指针指向的内存中。调用者不需要分配内存,因为 PAM 会管理用户名的存储。
  • prompt:这是一个可选的字符串参数,用于提示输入用户名。如果传递 NULL,则使用 PAM 配置中定义的默认提示符。如果指定了一个提示符,该提示符会在需要输入用户名时显示给用户。
  • 返回值:
    • PAM_SUCCESS:表示操作成功,用户名已成功获取。
    • 其他返回值:表示出现错误,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_CONV_ERR:对话失败。
      • PAM_BUF_ERR:内存缓冲区错误。
      • PAM_PERM_DENIED:权限被拒绝。
      • PAM_USER_UNKNOWN:用户未知。

# 2.1.6 pam_syslog()

用于将日志消息发送到系统日志。这个函数提供了一种方便的方法来记录 PAM 模块中的事件和错误。

函数原型:

1
2
3
4
#include <security/pam_appl.h>
#include <security/pam_ext.h>

void pam_syslog(const pam_handle_t *pamh, int priority, const char *fmt, ...);
  • pamh:这是一个指向 pam_handle_t 结构的指针,由 PAM 框架传递给模块。模块使用它来访问 PAM 会话的上下文信息。
  • priority:这是一个整数,指定日志消息的优先级。优先级由 syslog 定义的标准值表示,常见的优先级有:
    • LOG_EMERG:紧急情况,系统不可用。
    • LOG_ALERT:需要立即采取行动的情况。
    • LOG_CRIT:关键情况。
    • LOG_ERR:错误情况。
    • LOG_WARNING:警告情况。
    • LOG_NOTICE:正常但值得注意的情况。
    • LOG_INFO:信息性消息。
    • LOG_DEBUG:调试消息。
  • fmt:这是一个格式字符串,类似于 printf 的格式字符串,用于指定日志消息的格式。
  • ...:这是一个可变参数列表,包含与 fmt 格式字符串对应的值。

# 2.1.7 pam_get_item()

用于获取与 PAM 会话相关的特定信息项。通过这个函数,PAM 模块可以获取诸如用户名、TTY 设备、远程主机地址等信息,以便根据这些信息进行认证、授权等操作。

函数原型:

1
2
3
#include <security/pam_appl.h>

int pam_get_item(const pam_handle_t *pamh, int item_type, const void **item);
  • pamh:这是一个指向 pam_handle_t 结构的指针,用于表示当前 PAM 会话的上下文信息。
  • item_type:这是一个整数参数,用于指定要获取的信息项类型。常见的信息项类型包括:
    • PAM_SERVICE:服务名称,表示当前正在使用的 PAM 服务。
    • PAM_USER:用户名,表示当前用户的用户名。
    • PAM_TTY:TTY 设备名称,表示当前用户登录时使用的终端设备。
    • PAM_RHOST:远程主机地址,表示当前用户连接时的远程主机地址。
    • PAM_CONV:对话结构,包含用于进行用户交互的对话函数指针。
  • item:这是一个指向 const void * 类型的指针,用于存储获取到的信息。根据不同的信息项类型,item 指向的内容会有所不同。
  • 返回值:
    • PAM_SUCCESS:信息项获取成功。
    • 其他返回值:表示出现错误或操作失败,可以参考 PAM 手册获取错误代码的详细信息。

# 2.2 模块函数

模块函数主要是在编写PAM模块时需要实现的函数接口。

# 2.2.1 pam_sm_authenticate()

用于执行用户身份认证。每个 PAM 模块都可以实现这个函数来定义其特定的认证逻辑。

函数原型:

1
int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv);
  • pamh:这是一个指向 pam_handle_t 结构的指针,由 PAM 框架传递给模块。模块使用它来访问 PAM 会话的上下文信息。
  • flags:这是一个整数标志,用于控制认证行为。常用标志包括:
    • PAM_SILENT:静默模式,不生成任何输出。
    • PAM_DISALLOW_NULL_AUTHTOK:如果认证令牌为空,则不允许认证。
  • argcargv:这些参数用于传递 PAM 配置文件中的选项。argc 是选项的数量,argv 是一个包含选项的字符串数组。例如,在 PAM 配置文件中可能会有类似 auth required pam_unix.so debug,其中 debug 就是一个选项。
  • 返回值:
    • PAM_SUCCESS:表示认证成功。
    • 其他返回值:表示出现错误或认证失败,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_AUTH_ERR:认证失败。
      • PAM_USER_UNKNOWN:用户未知。
      • PAM_CRED_INSUFFICIENT:凭据不足。
      • PAM_AUTHINFO_UNAVAIL:认证信息不可用。

# 2.2.2 pam_sm_acct_mgmt()

用于执行账户管理任务。

函数原型:

1
int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv);
  • pamh:由 PAM 框架传递给模块。模块使用它来访问 PAM 会话的上下文信息。
  • flags:一个整数标志,用于控制账户管理行为。常用标志包括:
    • PAM_SILENT:静默模式,不生成任何输出。
  • argcargv:用于传递 PAM 配置文件中的选项。argc 是选项的数量,argv 是一个包含选项的字符串数组。例如,在 PAM 配置文件中可能会有类似 account required pam_unix.so debug,其中 debug 就是一个选项。
  • 返回值:pam_sm_acct_mgmt 函数返回一个整数,表示操作的结果:
    • PAM_SUCCESS:表示账户检查通过,没有问题。
    • 其他返回值:表示出现错误,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_ACCT_EXPIRED:账户已过期。
      • PAM_AUTH_ERR:认证失败。
      • PAM_PERM_DENIED:权限被拒绝。
      • PAM_NEW_AUTHTOK_REQD:需要更新认证令牌(例如密码过期需要更改)。

# 2.2.3 pam_sm_setcred()

用于设置用户凭据。这个函数通常在用户成功认证后被调用,用于初始化、刷新或销毁用户的认证凭据,如设置用户的口令、令牌等。通过实现这个函数,可以在用户成功认证后执行初始化、刷新或销毁用户凭据等操作。结合 pam_get_user 和日志记录函数,可以灵活处理用户信息并记录操作结果,提供可靠的认证凭据管理功能。

函数原型:

1
2
3
#include <security/pam_modules.h>

int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv);
  • pamh:这是一个指向 pam_handle_t 结构的指针,由 PAM 框架传递给模块。模块使用它来访问 PAM 会话的上下文信息。
  • flags:这是一个整数标志,用于控制凭据设置行为。常用标志包括:
    • PAM_ESTABLISH_CRED:设置新凭据。
    • PAM_DELETE_CRED:删除旧凭据。
    • PAM_REINITIALIZE_CRED:重新初始化凭据。
    • PAM_REFRESH_CRED:刷新凭据。
    • PAM_SILENT:静默模式,不生成任何输出。
  • argcargv:这些参数用于传递 PAM 配置文件中的选项。argc 是选项的数量,argv 是一个包含选项的字符串数组。例如,在 PAM 配置文件中可能会有类似 auth required pam_unix.so debug,其中 debug 就是一个选项。
  • 返回值:
    • PAM_SUCCESS:操作成功。
    • 其他返回值:表示出现错误或操作失败,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_CRED_UNAVAIL:凭据不可用。
      • PAM_CRED_EXPIRED:凭据已过期。
      • PAM_CRED_ERR:设置凭据时发生错误。
      • PAM_USER_UNKNOWN:未知用户。

# 2.2.4 pam_sm_open_session()

用于在用户会话开始时执行特定的操作。这个函数通常在用户成功登录并建立会话后被调用,用于初始化用户环境、记录登录事件等。通过实现这个函数,可以在用户成功登录并建立会话后执行初始化操作、记录登录事件等。结合 pam_get_user 和日志记录函数,可以灵活处理用户信息并记录操作结果,提供可靠的会话管理功能。

函数原型:

1
2
3
#include <security/pam_modules.h>

int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv);
  • pamh:这是一个指向 pam_handle_t 结构的指针,由 PAM 框架传递给模块。模块使用它来访问 PAM 会话的上下文信息。
  • flags:这是一个整数标志,用于控制会话打开行为。常用标志包括:
    • PAM_SILENT:静默模式,不生成任何输出。
  • argcargv:这些参数用于传递 PAM 配置文件中的选项。argc 是选项的数量,argv 是一个包含选项的字符串数组。例如,在 PAM 配置文件中可能会有类似 session required pam_unix.so debug,其中 debug 就是一个选项。
  • 返回值:
    • PAM_SUCCESS:会话打开成功。
    • 其他返回值:表示出现错误或操作失败,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_SESSION_ERR:会话错误。
      • PAM_PERM_DENIED:权限被拒绝。
      • PAM_IGNORE:忽略操作。

# 2.2.5 pam_sm_close_session()

用于在用户会话结束时执行特定的操作。这个函数通常在用户会话关闭之前被调用,用于清理会话状态、记录登出事件等。通过实现这个函数,可以在用户会话关闭之前执行清理操作、记录登出事件等。结合 pam_get_user 和日志记录函数,可以灵活处理用户信息并记录操作结果,提供可靠的会话管理功能。

函数原型:

1
2
3
#include <security/pam_modules.h>

int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv);
  • pamh:这是一个指向 pam_handle_t 结构的指针,由 PAM 框架传递给模块。模块使用它来访问 PAM 会话的上下文信息。
  • flags:这是一个整数标志,用于控制会话关闭行为。常用标志包括:
    • PAM_SILENT:静默模式,不生成任何输出。
  • argcargv:这些参数用于传递 PAM 配置文件中的选项。argc 是选项的数量,argv 是一个包含选项的字符串数组。例如,在 PAM 配置文件中可能会有类似 session required pam_unix.so debug,其中 debug 就是一个选项。
  • 返回值:
    • PAM_SUCCESS:会话关闭成功。
    • 其他返回值:表示出现错误或操作失败,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_SESSION_ERR:会话错误。
      • PAM_PERM_DENIED:权限被拒绝。
      • PAM_IGNORE:忽略操作。

# 2.2.6 pam_sm_chauthtok()

用于在用户修改身份验证令牌(例如密码)时执行特定的操作。这个函数通常在用户试图修改令牌时(例如更改密码)被调用,用于执行密码策略的检查、记录密码更改事件等。通过实现这个函数,可以在用户修改密码时执行密码策略的检查、记录密码更改事件等。结合 pam_get_user 和日志记录函数,可以灵活处理用户信息并记录操作结果,提供可靠的密码更改管理功能。

1
2
3
#include <security/pam_modules.h>

int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv);
  • pamh:这是一个指向 pam_handle_t 结构的指针,由 PAM 框架传递给模块。模块使用它来访问 PAM 会话的上下文信息。
  • flags:这是一个整数标志,用于控制密码更改行为。常用标志包括:
    • PAM_SILENT:静默模式,不生成任何输出。
  • argcargv:这些参数用于传递 PAM 配置文件中的选项。argc 是选项的数量,argv 是一个包含选项的字符串数组。例如,在 PAM 配置文件中可能会有类似 password requisite pam_unix.so minlen=8,其中 minlen=8 就是一个选项。
  • 返回值:
    • PAM_SUCCESS:密码更改成功。
    • 其他返回值:表示出现错误或操作失败,可以参考 PAM 手册获取错误代码的详细信息。常见错误包括:
      • PAM_AUTHTOK_ERR:身份验证令牌错误。
      • PAM_AUTHTOK_RECOVERY_ERR:身份验证令牌恢复错误。
      • PAM_AUTHTOK_LOCK_BUSY:身份验证令牌锁定繁忙。
      • PAM_IGNORE:忽略操作。

# 3、代码示例

# 3.1 检查当前用户密码

修改自Linux-pam源码中的示例check_user.c

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/*
  $Id$

  This program was contributed by Shane Watts <[email protected]>
  slight modifications by AGM.

  You need to add the following (or equivalent) to the /etc/pam.conf file.
  # check authorization
  check   auth       required     pam_unix_auth.so
  check   account    required     pam_unix_acct.so
*/

#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <stdio.h>
#include <sys/types.h>
#include <pwd.h>

static struct pam_conv conv = {
    misc_conv,
    NULL
};

int main(int argc, char *argv[])
{
        pam_handle_t *pamh=NULL;
        int retval;
        const char *user="nobody";

        if(argc == 1) {
                struct passwd *pw;
                uid_t uid = geteuid();
                pw = getpwuid(uid);
                if (pw) {
                        user = pw->pw_name;
                } else {
                        fprintf(stderr, "Get username fialed.\n");
				                exit(1);
                }
        } else if (argc == 2) {
                user = argv[1];
        } else if(argc > 2) {
                fprintf(stderr, "Usage: check_user [username]\n");
                exit(1);
        }
        
        printf("user: %s\n", user);

        retval = pam_start("check", user, &conv, &pamh);

        if (retval == PAM_SUCCESS)
                retval = pam_authenticate(pamh, 0);    /* is user really user? */

        if (retval == PAM_SUCCESS)
                retval = pam_acct_mgmt(pamh, 0);       /* permitted access? */

        /* This is where we have been authorized or not. */

        if (retval == PAM_SUCCESS) {
                fprintf(stdout, "Authenticated\n");
        } else {
                fprintf(stdout, "Not Authenticated\n");
        }

        if (pam_end(pamh,retval) != PAM_SUCCESS) {     /* close Linux-PAM */
                pamh = NULL;
                fprintf(stderr, "check_user: failed to release authenticator\n");
                exit(1);
        }

        return ( retval == PAM_SUCCESS ? 0:1 );       /* indicate success */
}

编译该文件:

1
gcc check_user.c -o check -lpam -lpam_misc

# 3.2 检查指定用户的用户名与密码

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <security/pam_modules.h>
#include <security/pam_appl.h>
#include <security/pam_misc.h>
#include <stdio.h>

static struct pam_conv conv = {
    misc_conv,
    NULL
};

int main(int argc, char *argv[])
{
        pam_handle_t *pamh=NULL;
        int retval;
        const char *user="nobody";

        retval = pam_start("check", NULL, &conv, &pamh);
        if (retval != PAM_SUCCESS) {
                fprintf(stderr, "pam start failed: %d\n", retval);
                return retval;
        }

        retval = pam_get_user(pamh, &user, "Username: ");
        if (retval != PAM_SUCCESS) {
                fprintf(stderr, "pam get user failed: %d\n", retval);
                goto output;
        }

        retval = pam_authenticate(pamh, 0);    /* is user really user? */
        if (retval != PAM_SUCCESS) {
                fprintf(stderr, "pam authenticate failed: %d\n", retval);
                goto output;
        }

        retval = pam_acct_mgmt(pamh, 0);       /* permitted access? */
        if (retval != PAM_SUCCESS) {
                fprintf(stderr, "pam acct mgmt failed: %d\n", retval);
                goto output;
        }

        if (retval == PAM_SUCCESS) {
                fprintf(stdout, "Authenticated\n");
        } else {
                fprintf(stdout, "Not Authenticated\n");
        }

output:
        if (pam_end(pamh,retval) != PAM_SUCCESS) {     /* close Linux-PAM */
                pamh = NULL;
                fprintf(stderr, "check_user: failed to release authenticator\n");
                exit(1);
        }

        return ( retval == PAM_SUCCESS ? 0:1 );       /* indicate success */
}

编译:

1
gcc check_user.c -Wall -o check -lpam -lpam_misc

验证:

1
2
3
4
$ ./check
Username: sunxidong
Password:
Authenticated

# 4、参考

Licensed under CC BY-NC-SA 4.0
最后更新于 2024-06-03 09:37 CST
网站已稳定运行 小时 分钟
使用 Hugo 构建
主题 StackJimmy 设计