美文网首页
Linux PAM 开发

Linux PAM 开发

作者: 这世界有我 | 来源:发表于2017-08-05 11:19 被阅读0次

    简介

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

    体系结构

    PAM架构框图
        整个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

    相关文章

      网友评论

          本文标题: Linux PAM 开发

          本文链接:https://www.haomeiwen.com/subject/hnpilxtx.html