Linux PAM 开发

xiaoxiao2021-02-28  121

简介

可插拔验证模块 (Pluggable authentication module, PAM) 为系统登录应用程序提供了验证和相关的安全服务。PAM是一种提供给应用程序通用的身份鉴别与认证,每一种应用程序可以通过编写PAM模块为自己设置访问控制规则。PAM的应用很好的解决了两个问题,一是避免了每一种应用程序都编写自己的访问控制模块,大量减少了重复开发,更重要的是将访问控制与应用程序本身相分离,这样即使以后发现控制算法有问题,也不用重新改写应用程序,只将PAM模块更新替换即可。

PAM作为一种可插拔的模块,实现了认证与操作系统以及应用程序的分离,十分灵活,并且开发方便。并且PAM提供了API接口,应用程序可以方便的调用他们。

体系结构

整个PAM分为三部分,最上层是应用程序,如sshd,login等系统自带的程序都已经支持PAM,我们也可以编写自己的应用程序。最下层是PAM额认证模块,总共有四种服务。模块中有编写好的PAM SPI,封装了具体的认证逻辑。中间的PAM库为应用程序提供了PAM API可以供应用程序调用,并向下提供了一种PAM API到PAM SPI的映射,以及PAM配置文件的加载。

编写PAM应用程序分为三部分,应用程序,会话函数,底层服务模块。应用程序就是我们希望对外提供的程序如linux的sshd,sudo等,会话函数是连接应用程序与服务模块的桥梁,负责两者之间的对话。

服务模块开发

服务模块开发是最常用的,也非常简单。linux的服务模块都位于/lib64/security/目录下,包含了pam_unix.so,pam_env.so等,我们模块开发完成编译为.so以后放到此目录即可。 以sshd auth模块开发为例,我们获取ssh远程的token以后,希望实现我们自己的验证,简单代码如下。编写完成以后,执行

gcc pam_test.c -fPIC -shared -o pam_test.so

将pam_test.so 文件拷贝到/lib64/security/下,然后在/etc/pam.d/sshd文件下加入

auth sufficient pam_test.so

这是自己编译的pam模块已经能起到作用,加入配置文件的时候注意加入的位置,看好不同关键字sufficient,include,optional的含义。

sshd加入PAM模块一定要谨慎,搞不好就跟着机器永远拜拜了。在改PAM的时候记住留一个session不要关,如果sshs的PAM搞混乱,先将sshd_config文件的USE PAM功能关掉,再慢慢的解决问题。

#include <sys/param.h> #include <pwd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <security/pam_modules.h> #include <security/pam_appl.h> #include <libsven_thrift_client.h> #ifndef PAM_EXTERN #define PAM_EXTERN #endif PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,int argc, const char *argv[]) { struct passwd *pwd; const char *user; char *crypt_password, *password; int pam_err, retry; // identify user if ((pam_err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) return (pam_err); if ((pwd = getpwnam(user)) == NULL) return (PAM_USER_UNKNOWN); // get password for (retry = 0; retry < 3; ++retry) { pam_err = pam_get_authtok(pamh, PAM_AUTHTOK, (const char **)&password, NULL); if (pam_err == PAM_SUCCESS) break; } if (pam_err == PAM_CONV_ERR) return (pam_err); if (pam_err != PAM_SUCCESS) return (PAM_AUTH_ERR); /* auth password */ // auth_function() return (PAM_SUCCESS); } PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { return (PAM_SUCCESS); } PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { return (PAM_SUCCESS); } PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { return (PAM_SUCCESS); } PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { return (PAM_SUCCESS); } PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { return (PAM_SERVICE_ERR); }

会话函数开发

pam模块已经提编译好的会话函数,我们可以直接调用产生会话,如Google验证码提示输入pin。

struct pam_conv { int (*conv)(int num_msg, const struct pam_message **msg, struct pam_response **resp, void *appdata_ptr); void *appdata_ptr; };

上面是会话函数所处于的结构体,第一个参数就是回函函数,第二个参数是会话的上下文。该函数在PAM源码的/pamlib/misc_conv.c中。

int misc_conv(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr)

在PAM服务模块中对话的使用方式如下:

#include <sys/param.h> #include <pwd.h> #include <stdlib.h> #include <stdio.h> #include <string.h> #include <unistd.h> #include <security/pam_modules.h> #include <security/pam_appl.h> static char password_prompt[] = "Password:"; #ifndef PAM_EXTERN #define PAM_EXTERN #endif PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char *argv[]) { struct pam_conv *conv; struct pam_message msg; const struct pam_message *msgp; struct pam_response *resp; struct passwd *pwd; const char *user; char *crypt_password, *password; int pam_err, retry; /* identify user */ if ((pam_err = pam_get_user(pamh, &user, NULL)) != PAM_SUCCESS) return (pam_err); if ((pwd = getpwnam(user)) == NULL) return (PAM_USER_UNKNOWN); /* get password */ pam_err = pam_get_item(pamh, PAM_CONV, (const void **)&conv); if (pam_err != PAM_SUCCESS) return (PAM_SYSTEM_ERR); msg.msg_style = PAM_PROMPT_ECHO_OFF; msg.msg = password_prompt; msgp = &msg; for (retry = 0; retry < 3; ++retry) { resp = NULL; pam_err = (*conv->conv)(1, &msgp, &resp, conv->appdata_ptr); if (resp != NULL) { if (pam_err == PAM_SUCCESS) password = resp->resp; else free(resp->resp); free(resp); } if (pam_err == PAM_SUCCESS) break; } if (pam_err == PAM_CONV_ERR) return (pam_err); if (pam_err != PAM_SUCCESS) return (PAM_AUTH_ERR); /* compare passwords */ if ((!pwd->pw_passwd[0] && (flags & PAM_DISALLOW_NULL_AUTHTOK)) || (crypt_password = crypt(password, pwd->pw_passwd)) == NULL || strcmp(crypt_password, pwd->pw_passwd) != 0) pam_err = PAM_AUTH_ERR; else pam_err = PAM_SUCCESS; #ifndef _OPENPAM free(password); #endif return (pam_err); }

会话函数基本实现方式:

/* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "@(#)pam_tty_conv.c 1.4 05/02/12 SMI" #define __EXTENSIONS__ /* to expose flockfile and friends in stdio.h */ #include <errno.h> #include <libgen.h> #include <malloc.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <stropts.h> #include <unistd.h> #include <termio.h> #include <security/pam_appl.h> static int ctl_c; /* was the conversation interrupted? */ /* ARGSUSED 1 */ static void interrupt(int x) { ctl_c = 1; } /* getinput -- read user input from stdin abort on ^C * Entry noecho == TRUE, don't echo input. * Exit User's input. * If interrupted, send SIGINT to caller for processing. */ static char * getinput(int noecho) { struct termio tty; unsigned short tty_flags; char input[PAM_MAX_RESP_SIZE]; int c; int i = 0; void (*sig)(int); ctl_c = 0; sig = signal(SIGINT, interrupt); if (noecho) { (void) ioctl(fileno(stdin), TCGETA, &tty); tty_flags = tty.c_lflag; tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL); (void) ioctl(fileno(stdin), TCSETAF, &tty); } /* go to end, but don't overflow PAM_MAX_RESP_SIZE */ flockfile(stdin); while (ctl_c == 0 && (c = getchar_unlocked()) != '\n' && c != '\r' && c != EOF) { if (i < PAM_MAX_RESP_SIZE) { input[i++] = (char)c; } } funlockfile(stdin); input[i] = '\0'; if (noecho) { tty.c_lflag = tty_flags; (void) ioctl(fileno(stdin), TCSETAW, &tty); (void) fputc('\n', stdout); } (void) signal(SIGINT, sig); if (ctl_c == 1) (void) kill(getpid(), SIGINT); return (strdup(input)); } /* Service modules do not clean up responses if an error is returned. * Free responses here. */ static void free_resp(int num_msg, struct pam_response *pr) { int i; struct pam_response *r = pr; if (pr == NULL) return; for (i = 0; i < num_msg; i++, r++) { if (r->resp) { /* clear before freeing -- may be a password */ bzero(r->resp, strlen(r->resp)); free(r->resp); r->resp = NULL; } } free(pr); } /* ARGSUSED */ int pam_tty_conv(int num_msg, struct pam_message **mess, struct pam_response **resp, void *my_data) { struct pam_message *m = *mess; struct pam_response *r; int i; if (num_msg <= 0 || num_msg >= PAM_MAX_NUM_MSG) { (void) fprintf(stderr, "bad number of messages %d " "<= 0 || >= %d\n", num_msg, PAM_MAX_NUM_MSG); *resp = NULL; return (PAM_CONV_ERR); } if ((*resp = r = calloc(num_msg, sizeof (struct pam_response))) == NULL) return (PAM_BUF_ERR); /* Loop through messages */ for (i = 0; i < num_msg; i++) { int echo_off; /* bad message from service module */ if (m->msg == NULL) { (void) fprintf(stderr, "message[%d]: %d/NULL\n", i, m->msg_style); goto err; } /* * fix up final newline: * removed for prompts * added back for messages */ if (m->msg[strlen(m->msg)] == '\n') m->msg[strlen(m->msg)] = '\0'; r->resp = NULL; r->resp_retcode = 0; echo_off = 0; switch (m->msg_style) { case PAM_PROMPT_ECHO_OFF: echo_off = 1; /*FALLTHROUGH*/ case PAM_PROMPT_ECHO_ON: (void) fputs(m->msg, stdout); r->resp = getinput(echo_off); break; case PAM_ERROR_MSG: (void) fputs(m->msg, stderr); (void) fputc('\n', stderr); break; case PAM_TEXT_INFO: (void) fputs(m->msg, stdout); (void) fputc('\n', stdout); break; default: (void) fprintf(stderr, "message[%d]: unknown type " "%d/val=\"%s\"\n", i, m->msg_style, m->msg); /* error, service module won't clean up */ goto err; } if (errno == EINTR) goto err; /* next message/response */ m++; r++; } return (PAM_SUCCESS); err: free_resp(i, r); *resp = NULL; return (PAM_CONV_ERR); }

上述总结中对以后进行了引用,感谢。 http://docs.oracle.com/cd/E24847_01/html/E22200/pam-01.html#scrolltoc

https://www.freebsd.org/doc/fr_FR.ISO8859-1/articles/pam/pam-sample-module.html

转载请注明原文地址: https://www.6miu.com/read-17775.html

最新回复(0)