Go to main content
Oracle® Solaris 11 セキュリティー開発者ガイド

印刷ビューの終了

更新: 2016 年 11 月
 
 

対話関数の記述

PAM モジュール (アプリケーション) はいくつかの方法でユーザーと通信できます。たとえば、コマンド行を使用する方法や、ダイアログボックスを使用する方法などです。その結果、ユーザーと通信する PAM コンシューマの設計者は、「対話関数」と呼ばれるものを記述する必要があります。対話関数は、特定の通信手段に依存することなしに、ユーザーとモジュール間でメッセージの受け渡しを行います。対話関数は、対話関数コールバック関数の pam_message パラメータ内の msg_style パラメータから、メッセージタイプを得ます。pam_start(3PAM) のマニュアルページを参照してください。

開発者は、PAM とユーザー間の通信手段について、何らかの仮定を行なってはいけません。むしろ、アプリケーションは、処理が完了するまでユーザーとメッセージを交換し続ける必要があります。アプリケーションは、対話関数のメッセージ文字列を、解釈または変更することなしに表示します。個々のメッセージには、複数の行を含めることができるほか、制御文字や余分な空白も含めることができます。対話関数に送信する文字列を各言語対応にすることは、サービスモジュールの責任であることに注意してください。

    対話関数の例 pam_tty_conv() を、次に示します。pam_tty_conv() の引数は次のとおりです。

  • num_msg – この関数に渡されるメッセージの数。

  • **mess – ユーザーからのメッセージを格納するバッファーへのポインタ

  • **resp – ユーザーへの応答を格納するバッファーへのポインタ。

  • *my_data – アプリケーションデータへのポインタ。

この関数例は、stdin からユーザー入力を取得します。応答バッファーに対するメモリーの割り当ては、このルーチンが行う必要があります。最大値 PAM_MAX_NUM_MSG を設定すれば、メッセージの数を制限できます。対話関数がエラーを返す場合、対話関数は応答に割り当てられていたすべてのメモリーをクリアおよび解放する役割を担います。さらに、対話関数は応答ポインタを NULL に設定する必要があります。メモリーのクリアは、ゼロ埋めアプローチを使用して完了されるべきです。対話関数の呼び出し側には、呼び出し側に返されたすべての応答を解放する責任があります。対話を実現するために、この関数は、ユーザーアプリケーションからのメッセージをループ処理します。有効なメッセージは stdout に書き込まれ、エラーメッセージは stderr に書き込まれます。

使用例 5  PAM 対話関数
/*
 * Copyright (c) 2005, 2012, Oracle and/or its affiliates. All rights reserved.
 */

#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);

    errno = 0; /* don't propogate possible EINTR */



    /* 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);
}