Linux PAM 開發(fā)

簡介

   可插拔驗證模塊 (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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捶朵,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子品腹,更是在濱河造成了極大的恐慌红碑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,546評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件镣典,死亡現(xiàn)場離奇詭異唾琼,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)锡溯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,224評論 3 395
  • 文/潘曉璐 我一進(jìn)店門祭饭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芜茵,“玉大人倡蝙,你說我怎么就攤上這事≈砼ィ” “怎么了胆建?”我有些...
    開封第一講書人閱讀 164,911評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長扑馁。 經(jīng)常有香客問我凉驻,道長,這世上最難降的妖魔是什么涝登? 我笑而不...
    開封第一講書人閱讀 58,737評論 1 294
  • 正文 為了忘掉前任缀拭,我火速辦了婚禮,結(jié)果婚禮上蛛淋,老公的妹妹穿的比我還像新娘。我一直安慰自己褐荷,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,753評論 6 392
  • 文/花漫 我一把揭開白布层宫。 她就那樣靜靜地躺著其监,像睡著了一般。 火紅的嫁衣襯著肌膚如雪毁菱。 梳的紋絲不亂的頭發(fā)上锌历,一...
    開封第一講書人閱讀 51,598評論 1 305
  • 那天,我揣著相機(jī)與錄音究西,去河邊找鬼。 笑死遮斥,一個胖子當(dāng)著我的面吹牛商膊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晕拆,決...
    沈念sama閱讀 40,338評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼实幕,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了昆庇?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,249評論 0 276
  • 序言:老撾萬榮一對情侶失蹤拱撵,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后拴测,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,696評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡屿愚,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,888評論 3 336
  • 正文 我和宋清朗相戀三年务荆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片娱据。...
    茶點故事閱讀 40,013評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡浦箱,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咽安,到底是詐尸還是另有隱情,我是刑警寧澤妆棒,帶...
    沈念sama閱讀 35,731評論 5 346
  • 正文 年R本政府宣布沸伏,位于F島的核電站,受9級特大地震影響红选,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜喇肋,卻給世界環(huán)境...
    茶點故事閱讀 41,348評論 3 330
  • 文/蒙蒙 一迹辐、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧间学,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,929評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鹊漠,卻和暖如春茶行,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背畔师。 一陣腳步聲響...
    開封第一講書人閱讀 33,048評論 1 270
  • 我被黑心中介騙來泰國打工看锉, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留姿锭,地道東北人伯铣。 一個月前我還...
    沈念sama閱讀 48,203評論 3 370
  • 正文 我出身青樓腔寡,卻偏偏與公主長得像焚鲜,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子忿磅,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,960評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法凭语,類相關(guān)的語法,內(nèi)部類的語法吨些,繼承相關(guān)的語法,異常的語法锤灿,線程的語...
    子非魚_t_閱讀 31,634評論 18 399
  • 動態(tài)鏈接辆脸,在可執(zhí)行文件裝載時或運行時,由操作系統(tǒng)的裝載程序加載庫状囱。大多數(shù)操作系統(tǒng)將解析外部引用(比如庫)作為加載過...
    小5筒閱讀 5,507評論 0 3
  • 一. Java基礎(chǔ)部分.................................................
    wy_sure閱讀 3,811評論 0 11
  • 今天是2017年9月5日 是【曉暉有話說】陪伴您的兩百五十五天 親愛的友人,你好: 見字如面: 【從小城出發(fā)】中午...
    暉暉曉閱讀 188評論 0 0
  • 添加對Interface Builder的支持 如果你在Interface Builder中看rating控件袭艺,你...
    raingu24閱讀 1,191評論 0 1