簡介
可插拔驗證模塊 (Pluggable authentication module, PAM) 為系統(tǒng)登錄應(yīng)用程序提供了驗證和相關(guān)的安全服務(wù)袄膏。PAM是一種提供給應(yīng)用程序通用的身份鑒別與認(rèn)證,每一種應(yīng)用程序可以通過編寫PAM模塊為自己設(shè)置訪問控制規(guī)則沉馆。PAM的應(yīng)用很好的解決了兩個問題,一是避免了每一種應(yīng)用程序都編寫自己的訪問控制模塊揖盘,大量減少了重復(fù)開發(fā)锌奴,更重要的是將訪問控制與應(yīng)用程序本身相分離,這樣即使以后發(fā)現(xiàn)控制算法有問題箕慧,也不用重新改寫應(yīng)用程序,只將PAM模塊更新替換即可颠焦。
PAM作為一種可插拔的模塊,實現(xiàn)了認(rèn)證與操作系統(tǒng)以及應(yīng)用程序的分離粉渠,十分靈活圾另,并且開發(fā)方便。并且PAM提供了API接口淳衙,應(yīng)用程序可以方便的調(diào)用他們饺著。
體系結(jié)構(gòu)
PAM架構(gòu)框圖
整個PAM分為三部分,最上層是應(yīng)用程序靴跛,如sshd渡嚣,login等系統(tǒng)自帶的程序都已經(jīng)支持PAM,我們也可以編寫自己的應(yīng)用程序识椰。最下層是PAM額認(rèn)證模塊,總共有四種服務(wù)藏畅。模塊中有編寫好的PAM SPI功咒,封裝了具體的認(rèn)證邏輯。中間的PAM庫為應(yīng)用程序提供了PAM API可以供應(yīng)用程序調(diào)用榜旦,并向下提供了一種PAM API到PAM SPI的映射景殷,以及PAM配置文件的加載澡屡。
編寫PAM應(yīng)用程序分為三部分藕届,應(yīng)用程序,會話函數(shù)休偶,底層服務(wù)模塊。應(yīng)用程序就是我們希望對外提供的程序如linux的sshd词顾,sudo等碱妆,會話函數(shù)是連接應(yīng)用程序與服務(wù)模塊的橋梁,負(fù)責(zé)兩者之間的對話。
服務(wù)模塊開發(fā)
服務(wù)模塊開發(fā)是最常用的上忍,也非常簡單纳本。linux的服務(wù)模塊都位于/lib64/security/目錄下,包含了pam_unix.so,pam_env.so等繁成,我們模塊開發(fā)完成編譯為.so以后放到此目錄即可巾腕。
以sshd auth模塊開發(fā)為例,我們獲取ssh遠(yuǎn)程的token以后尊搬,希望實現(xiàn)我們自己的驗證,簡單代碼如下亲茅。編寫完成以后狗准,執(zhí)行
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模塊已經(jīng)能起到作用,加入配置文件的時候注意加入的位置捞附,看好不同關(guān)鍵字sufficient,include鸟召,optional的含義欧募。
sshd加入PAM模塊一定要謹(jǐn)慎,搞不好就跟著機(jī)器永遠(yuǎn)拜拜了跟继。在改PAM的時候記住留一個session不要關(guān),如果sshs的PAM搞混亂娱两,先將sshd_config文件的USE PAM功能關(guān)掉金吗,再慢慢的解決問題。
#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);
}
會話函數(shù)開發(fā)
pam模塊已經(jīng)提編譯好的會話函數(shù),我們可以直接調(diào)用產(chǎn)生會話跟匆,如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;
};
上面是會話函數(shù)所處于的結(jié)構(gòu)體烤蜕,第一個參數(shù)就是回函函數(shù)迹冤,第二個參數(shù)是會話的上下文。該函數(shù)在PAM源碼的/pamlib/misc_conv.c中橱鹏。
int misc_conv(int num_msg, const struct pam_message **msgm,
struct pam_response **response, void *appdata_ptr)
在PAM服務(wù)模塊中對話的使用方式如下:
#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);
}
會話函數(shù)基本實現(xiàn)方式:
/*
* 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);
}
上述總結(jié)中對以后進(jìn)行了引用礁竞,感謝。
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