プラグイン可能な認証モジュール (Pluggable Authentication Modules、PAM) は、システムエントリアプリケーションに対し、認証および関連セキュリティーサービスを提供します。この章の対象読者は、認証、アカウント管理、セッション管理、およびパスワード管理を PAM モジュール経由で行いたいと考えているシステムエントリアプリケーション開発者です。また、ここでは、PAM サービスモジュールの設計者向けの情報も記載します。この章では、次の内容について説明します。
PAM は最初、Sun によって開発されました。その後、X/Open (現在の Open Group) に PAM 仕様が提出されました。PAM 仕様は、『X/Open Single Sign-On Service (XSSO) - Pluggable Authentication』 (Open Group, UK ISBN 1-85912-144-6 June 1997) として入手可能となっています。PAM の Solaris 実装については、pam(3PAM)、libpam(3LIB)、および pam_sm(3PAM) のマニュアルページを参照してください。
PAM コンシューマ
PAM ライブラリ
pam.conf(4) 構成ファイル
PAM サービスモジュール。プロバイダとも呼ばれる
このフレームワークは、認証関連アクティビティーの統一的な実施手段を提供します。このアプローチを使えば、アプリケーション開発者は、PAM サービスのポリシーの意味を知らなくてもサービスを使用できるようになります。アルゴリズムは一元的に提供されます。アルゴリズムの変更は、個々のアプリケーションとは無関係に行えます。PAM を使えば、管理者は、アプリケーションを変更しないで、特定システムのニーズに合わせて認証プロセスを調整できるようになります。この調整は、PAM 構成ファイル pam.conf を通じて行われます。
次の図は、PAM のアーキテクチャーを示したものです。アプリケーションは、PAM アプリケーションプログラミングインタフェース (API) 経由で PAM ライブラリと通信します。PAM モジュールは、PAM サービスプロバイダインタフェース (SPI) 経由で PAM ライブラリと通信します。したがって、PAM ライブラリを使えば、アプリケーションとモジュールとの相互通信を実現できます。
PAM サービスモジュールは、login、rlogin、telnet などのシステムエントリアプリケーションに対し、認証およびその他のセキュリティーサービスを提供する共有ライブラリです。PAM サービスには次の 4 種類があります。
認証サービスモジュール – アカウントまたはサービスへのアクセス許可をユーザーに与えるためのモジュール。このサービスを提供するモジュールは、ユーザーの認証およびユーザー資格の設定を行います。
アカウント管理モジュール – 現在のユーザーのアカウントが有効かどうかを決定するためのモジュール。このサービスを提供するモジュールは、パスワードまたはアカウントの有効期間を検査できるほか、時間制限付きアクセスの検査も行えます。
セッション管理モジュール – ログインセッションの設定と終了を行うためのモジュール。
パスワード管理モジュール – パスワードの強度規則の適用と認証トークンの更新を行うためのモジュール。
1 つの PAM モジュールには、上記サービスを 1 つ以上実装できます。ただし、作業内容が明確に定義された単純なモジュールを使用すれば、設定の柔軟性が増します。したがって、PAM サービスは個別のモジュールとして実装することをお勧めします。各サービスは必要に応じて使用できます。それには、pam.conf(4) ファイル内で必要な定義を行います。
たとえば、Solaris OS に付属する pam_authtok_check(5) モジュールを使えば、システム管理者はサイトのパスワードポリシーを設定できます。pam_authtok_check(5) モジュールは、提案されたパスワードを、さまざまな強度条件に基づいて検査します。
Solaris PAM モジュールの完全な一覧については、『man pages section 5: Standards, Environments, and Macros』を参照してください。PAM モジュールの接頭辞は pam_ です。
PAM ライブラリ libpam(3LIB) は、PAM アーキテクチャーの主要構成要素です。
libpam は pam(3PAM) API をエクスポートします。アプリケーションは、この API を呼び出すことで、認証、アカウント管理、資格の確立、セッション管理、およびパスワード変更を行えます。
libpam はマスター構成ファイル pam.conf(4) をインポートします。PAM 構成ファイルでは、利用可能なサービスごとに PAM モジュール要件が指定されます。pam.conf はシステム管理者によって管理されます。
libpam は、サービスモジュールによってエクスポートされた pam_sm(3PAM) SPI をインポートします。
コンシューマが PAM ライブラリを使ってユーザー認証を行う例として、login がユーザー認証を行う手順を次に示します。
login アプリケーションは、PAM セッションを開始するために、pam_start(3PAM) を呼び出し、login サービスを指定します。
アプリケーションは、PAM ライブラリ libpam(3LIB) によってエクスポートされた PAM API に含まれる pam_authenticate(3PAM) を呼び出します。
ライブラリは、pam.conf ファイル内で login エントリを検索します。
PAM ライブラリは、pam.conf 内で login サービス用として構成されたモジュールごとに、pam_sm_authenticate(3PAM) を呼び出します。pam_sm_authenticate() は PAM SPI に含まれる関数です。pam.conf 制御フラグと各呼び出しの結果によって、ユーザーがシステムへのアクセスを許可されるかどうかが決まります。このプロセスについての詳細は、『Solaris のシステム管理 (セキュリティサービス)』の「PAM の構成 (参照)」を参照してください。
PAM ライブラリはこのような方法で、PAM アプリケーションを、システム管理者によって構成された PAM モジュールへと接続します。
PAM コンシューマは PAM ライブラリ libpam とリンクする必要があります。各モジュールが提供するサービスをアプリケーションから利用するには、pam_start(3PAM) を呼び出して PAM ライブラリのインスタンスを初期化する必要があります。pam_start() を呼び出すと、ハンドルが初期化されます。このハンドルは、後続の PAM 呼び出し時に毎回指定する必要があります。アプリケーション内での PAM サービスの利用を終了する際には、pam_end() を呼び出し、PAM ライブラリが使用したすべてのデータをクリーンアップします。
PAM アプリケーションと PAM モジュール間の通信は、「アイテム」経由で実現されます。たとえば、初期化時に使うと便利なアイテムを、次に示します。
PAM_USER – 現在の認証ユーザー
PAM_AUTHTOK – パスワード
PAM_USER_PROMPT – ユーザー名プロンプト
PAM_TTY – ユーザーが通信に使用している端末
PAM_REPOSITORY – ユーザーのアカウントリポジトリに対するすべての制限
PAM_REPOSITORY – ユーザーのアカウントリポジトリに対するすべての制限
PAM_RESOURCE – リソースに対するすべての制御
利用可能なすべてのアイテムの一覧については、pam_set_item(3PAM) のマニュアルページを参照してください。アプリケーションからアイテムを設定するには、pam_set_item (3PAM) を使用します。モジュールによって設定された値をアプリケーション内で取り出すには、pam_get_item(3PAM) を使用します。ただし、PAM_AUTHTOK と PAM_OLDAUTHTOK はアプリケーションから取得できません。また、PAM_SERVICE アイテムは設定できません。
login、rlogin、su、cron などのシステムサービスに対する PAM サービスモジュールを構成するには、PAM 構成ファイル pam.conf(4) を使用します。システム管理者がこのファイルを管理します。pam.conf 内のエントリの順番が間違っていると、予期しない副作用が生じる可能性があります。たとえば、pam.conf の設定が不適切であると、ユーザーがロックアウトされ、修復のためにシングルユーザーモードが必要になる可能性があります。PAM 構成については、『Solaris のシステム管理 (セキュリティサービス)』の「PAM の構成 (参照)」を参照してください。
ここでは、いくつかの PAM 関数を使用するアプリケーション例を示します。
次の PAM コンシューマアプリケーションは、例示目的で提供されています。この例は、端末へのアクセスを試みるユーザーを検証する基本的な端末ロックアプリケーションです。この例では、次の手順を実行します。
PAM セッションを初期化します。
PAM セッションは、pam_start(3PAM) 関数の呼び出しによって初期化されます。PAM コンシューマアプリケーションは、ほかの PAM 関数を呼び出す前に PAM セッションを最初に確立する必要があります。pam_start(3PAM) 関数は、次の引数を使用します。
plock – サービス名、つまり、アプリケーションの名前。サービス名は、PAM フレームワークで構成ファイル /etc/pam.conf 内のどの規則が適切かを決定するために使用されます。通常、サービス名はロギングとエラーレポートに使用されます。
pw->pw_name – ユーザー名は、PAM フレームワークの対象となるユーザーの名前です。
&conv – 対話関数 conv は、PAM がユーザーまたはアプリケーションと通信するための汎用手段を提供しま す。PAM モジュールは通信の実施方法を認識する方法を持っていないため、対話関数が必要です。通信は、GUI、コマンド行、スマートカードリーダー、またはその他のデバイスを使用して行うことができます。詳細は、「対話関数の記述」を参照してください。
&pamh – PAM ハンドル pamh は、PAM フレームワークで現在の処理に関する情報を格納する際に使用される不透明なハンドルです。このハンドルは、pam_start() の呼び出しが成功することによって返されます。
PAM インタフェースを呼び出すアプリケーションは、認証、パスワードの変更、プロセス資格操作、監査状態の初期化など、すべての必要な処理を実行するための十分な特権を持っている必要があります。この例では、ローカルユーザーのパスワードを検証するために、アプリケーションは /etc/shadow を読み取り可能でなければなりません。
ユーザーを認証します。
アプリケーションは、pam_authenticate(3PAM) を呼び出して現在のユーザーを認証します。一般に、ユーザーは認証サービスの種類に応じて、パスワードまたはその他の認証トークンを入力する必要があります。PAM フレームワークは /etc/pam.conf 内の認証サービス auth にリストされているモジュールを起動します。サービス名 plock は、使用する pam.conf エントリを決定する際に使用されます。plock のエントリが存在しない場合は、デフォルトで other のエントリが使用されます。アプリケーション構成ファイルで NULL パスワードが明示的に禁じられている場合は、PAM_DISALLOW_NULL_AUTHTOK フラグが渡されます。Solaris アプリケーションは、/etc/default/login の PASSREQ=YES 設定を確認します。
アカウントの有効性を確認します。
例では、pam_acct_mgmt(3PAM) 関数を使用して、認証ユーザーのアカウントの有効性を確認します。この例では、pam_acct_mgmt() がパスワードの有効期限を確認します。
pam_acct_mgmt() 関数は、PAM_DISALLOW_NULL_AUTHTOK フラグも使用します。pam_acct_mgmt() が PAM_NEW_AUTHTOK_REQD を返す場合は、認証ユーザーにパスワードの変更を許可するために、pam_chauthtok(3PAM) が呼び出されます。
パスワードの有効期限が切れていることがシステムによって検出された場合は、ユーザーにパスワードの変更を強制します。
例では、「成功」が返されるまでループを使用して pam_chauthtok() を呼び出します。ユーザーが認証情報 (通常はパスワード) の変更に成功した場合、pam_chauthtok() 関数は「成功」を返します。この例では、「成功」が返されるまでループが継続します。通常はアプリケーションで、終了するまでの最大試行回数を設定します。
pam_setcred(3PAM) を呼び出します。
pam_setcred(3PAM) 関数は、ユーザー資格の確立、変更、削除を行う際に使用されます。pam_setcred() は通常、ユーザー認証の完了時に呼び出されます。この呼び出しは、アカウントの検証完了後、セッションのオープン前に行われます。新しいユーザーセッションを確立する場合は、pam_setcred() 関数で PAM_ESTABLISH_CRED フラグを使用します。lockscreen の場合のように、セッションが既存セッションの更新版である場合、pam_setcred() を PAM_REFRESH_CRED フラグとともに呼び出すべきです。su を使用したり特定の役割を引き受けたりする場合のように、セッションが資格を変更する場合、pam_setcred() を PAM_REINITIALIZE_CRED フラグとともに呼び出すべきです。
PAM セッションをクローズします。
PAM セッションは、pam_end(3PAM) 関数の呼び出しによってクローズします。また、pam_end() は、すべての PAM リソースを解放します。
この PAM コンシューマアプリケーション例のソースコードを、次に示します。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
/* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include <sys/types.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <strings.h> #include <signal.h> #include <pwd.h> #include <errno.h> #include <security/pam_appl.h> extern int pam_tty_conv(int num_msg, struct pam_message **msg, struct pam_response **response, void *appdata_ptr); /* Disable keyboard interrupts (Ctrl-C, Ctrl-Z, Ctrl-\) */ static void disable_kbd_signals(void) { (void) signal(SIGINT, SIG_IGN); (void) signal(SIGTSTP, SIG_IGN); (void) signal(SIGQUIT, SIG_IGN); } /* Terminate current user session, i.e., logout */ static void logout() { pid_t pgroup = getpgrp(); (void) signal(SIGTERM, SIG_IGN); (void) fprintf(stderr, "Sorry, your session can't be restored.\n"); (void) fprintf(stderr, "Press return to terminate this session.\n"); (void) getchar(); (void) kill(-pgroup, SIGTERM); (void) sleep(2); (void) kill(-pgroup, SIGKILL); exit(-1); } int /*ARGSUSED*/ main(int argc, char *argv) { struct pam_conv conv = { pam_tty_conv, NULL }; pam_handle_t *pamh; struct passwd *pw; int err; disable_kbd_signals(); if ((pw = getpwuid(getuid())) == NULL) { (void) fprintf(stderr, "plock: Can't get username: %s\n", strerror(errno)); exit(1); } /* Initialize PAM framework */ err = pam_start("plock", pw->pw_name, &conv, &pamh); if (err != PAM_SUCCESS) { (void) fprintf(stderr, "plock: pam_start failed: %s\n", pam_strerror(pamh, err)); exit(1); } /* Authenticate user in order to unlock screen */ do { (void) fprintf(stderr, "Terminal locked for %s. ", pw->pw_name); err = pam_authenticate(pamh, 0); if (err == PAM_USER_UNKNOWN) { logout(); } else if (err != PAM_SUCCESS) { (void) fprintf(stderr, "Invalid password.\n"); } } while (err != PAM_SUCCESS); /* Make sure account and password are still valid */ switch (err = pam_acct_mgmt(pamh, 0)) { case PAM_SUCCESS: break; case PAM_USER_UNKNOWN: case PAM_ACCT_EXPIRED: /* User not allowed in anymore */ logout(); break; case PAM_NEW_AUTHTOK_REQD: /* The user's password has expired. Get a new one */ do { err = pam_chauthtok(pamh, 0); } while (err == PAM_AUTHTOK_ERR); if (err != PAM_SUCCESS) logout(); break; default: logout(); } if (pam_setcred(pamh, PAM_REFRESH_CRED) != PAM_SUCCESS){ logout(); } (void) pam_end(pamh, 0); return(0); /*NOTREACHED*/ }
前記の単純なアプリケーション 例 3–1 では、主要 PAM 関数のうちほんの数種類しか使用されていません。ここでは、その他の有用な PAM 関数をいくつか紹介します。
pam_open_session(3PAM) 関数は、ユーザー認証が成功したあと、新しいセッションをオープンする際に呼び出されます。
pam_getenvlist(3PAM) 関数は、新しい環境を確立する際に呼び出されます。pam_getenvlist() は、既存環境にマージすべき新しい環境を返します。
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 に書き込まれます。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
/* * 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); }
ここでは、PAM サービスモジュールの記述方法を説明します。
PAM サービスモジュールは、pam_get_item(3PAM) と pam_set_item(3PAM) を使ってアプリケーションとの通信を行います。サービスモジュール同士の通信には、pam_get_data(3PAM) と pam_set_data(3PAM) が使用されます。同一プロジェクト内のサービスモジュール間でデータを交換する必要がある場合、そのプロジェクト内で一意に決まるデータ名を確立する必要があります。その後、サービスモジュールは、関数 pam_get_data() と pam_set_data() を使ってそのデータを共有できます。
サービスモジュールは、次の 3 種類の PAM 戻りコードのいずれかを返す必要があります。
PAM_SUCCESS: 要求されているポリシーに合致しているという肯定的な決定を、モジュールが行なった場合。
PAM_IGNORE: モジュールがポリシー決定を行わなかった場合。
PAM_error: モジュールが参加していた決定が失敗した場合。error は、汎用エラーコード、サービスモジュールタイプに固有のコードのいずれかです。別のサービスモジュールタイプのエラーコードは使えません。エラーコードについては、pam_sm_module-type のマニュアルページを参照してください。
1 つのサービスモジュール内に複数の機能が含まれている場合、それらの機能はそれぞれ個別のモジュールに分割することをお勧めします。そうすることで、システム管理者は、ポリシー設定時によりきめ細かい制御を行えるようになります。
新しいサービスモジュールを作成した場合、対応するマニュアルページを提供する必要があります。マニュアルページには、次の情報を含める必要があります。
モジュールが受け入れる引数。
モジュールが実装しているすべての関数。
アルゴリズムに対するフラグの効果。
必要とされるすべての PAM アイテム。
このモジュールに固有のエラー戻りコード。
サービスモジュールは、メッセージを抑制するための PAM_SILENT フラグを尊重することが求められます。デバッグ情報を syslog に記録するには、debug 引数を指定することをお勧めします。デバッグ情報を記録するには、syslog(3C) 使用時に LOG_AUTH と LOG_DEBUG を指定します。その他のメッセージは、LOG_AUTH と適切な優先度を指定して syslog() に送るべきです。openlog(3C)、closelog(3C)、および setlogmask(3C) という 3 つの関数はアプリケーションの設定に悪影響を与えるので、決して使用しないでください。
ここでは、PAM サービスモジュールの例を示します。この例では、ユーザーがこのサービスへのアクセスが許可されているグループのメンバーかどうかを確認します。プロバイダは、成功の場合にアクセスを許可し、失敗の場合にエラーメッセージをログに記録します。この例では、次の手順を実行します。
/etc/pam.conf の構成行からこのモジュールに渡されたオプションを解析します。
このモジュールは、nowarn オプションおよび debug オプション、さらに固有のオプション group を受け入れます。group オプションを使用する場合、デフォルトで使用されるグループ root 以外の特定のグループに対してアクセスを許可するようにモジュールを構成できます。この例については、ソースコードの DEFAULT_GROUP の定義を参照してください。たとえば、グループ staff に属するユーザーによる telnet(1) アクセスを許可するには、/etc/pam.conf の telnet スタックにある次の行を使用できます。
telnet account required pam_members_only.so.1 group=staff
ユーザー名、サービス名、およびホスト名を取得します。
ユーザー名は、現在のユーザー名を PAM ハンドルから取り出す、pam_get_user(3PAM) の呼び出しによって取得されます。ユーザー名が設定されていない場合、アクセスは拒否されます。サービス名とホスト名は、pam_get_item(3PAM) の呼び出しによって取得されます。
有効にする情報を検証します。
ユーザー名が設定されていない場合、アクセスは拒否されます。有効にするグループが定義されていない場合、アクセスは拒否されます。
現在のユーザーが、このホストへのアクセスが許可されている特殊グループのメンバーであることを確認し、アクセスを許可します。
固有のグループが定義されていてもメンバーが 1 つも含まれていない場合は、このモジュールがどのアカウント検証プロセスにも参加しないことを示す、PAM_IGNORE が返されます。決定は、スタック上のほかのモジュールに委ねられます。
ユーザーが特殊グループのメンバーではない場合は、アクセスが拒否されたことをユーザーに知らせるメッセージを表示します。
メッセージをログに記録してこのイベントを記録します。
次の例は、PAM プロバイダ例のソースコードです。
このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。
/* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include <stdio.h> #include <stdlib.h> #include <grp.h> #include <string.h> #include <syslog.h> #include <libintl.h> #include <security/pam_appl.h> /* * by default, only users who are a member of group "root" are allowed access */ #define DEFAULT_GROUP "root" static char *NOMSG = "Sorry, you are not on the access list for this host - access denied."; int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc, const char **argv) { char *user = NULL; char *host = NULL; char *service = NULL; const char *allowed_grp = DEFAULT_GROUP; char grp_buf[4096]; struct group grp; struct pam_conv *conversation; struct pam_message message; struct pam_message *pmessage = &message; struct pam_response *res = NULL; int i; int nowarn = 0; int debug = 0; /* Set flags to display warnings if in debug mode. */ for (i = 0; i < argc; i++) { if (strcasecmp(argv[i], "nowarn") == 0) nowarn = 1; else if (strcasecmp(argv[i], "debug") == 0) debug = 1; else if (strncmp(argv[i], "group=", 6) == 0) allowed_grp = &argv[i][6]; } if (flags & PAM_SILENT) nowarn = 1; /* Get user name,service name, and host name. */ (void) pam_get_user(pamh, &user, NULL); (void) pam_get_item(pamh, PAM_SERVICE, (void **) &service); (void) pam_get_item(pamh, PAM_RHOST, (void **) &host); /* Deny access if user is NULL. */ if (user == NULL) { syslog(LOG_AUTH|LOG_DEBUG, "%s: members_only: user not set", service); return (PAM_USER_UNKNOWN); } if (host == NULL) host = "unknown"; /* * Deny access if vuser group is required and user is not in vuser * group */ if (getgrnam_r(allowed_grp, &grp, grp_buf, sizeof (grp_buf)) == NULL) { syslog(LOG_NOTICE|LOG_AUTH, "%s: members_only: group \"%s\" not defined", service, allowed_grp); return (PAM_SYSTEM_ERR); } /* Ignore this module if group contains no members. */ if (grp.gr_mem[0] == 0) { if (debug) syslog(LOG_AUTH|LOG_DEBUG, "%s: members_only: group %s empty: " "all users allowed.", service, grp.gr_name); return (PAM_IGNORE); } /* Check to see if user is in group. If so, return SUCCESS. */ for (; grp.gr_mem[0]; grp.gr_mem++) { if (strcmp(grp.gr_mem[0], user) == 0) { if (debug) syslog(LOG_AUTH|LOG_DEBUG, "%s: user %s is member of group %s. " "Access allowed.", service, user, grp.gr_name); return (PAM_SUCCESS); } } /* * User is not a member of the group. * Set message style to error and specify denial message. */ message.msg_style = PAM_ERROR_MSG; message.msg = gettext(NOMSG); /* Use conversation function to display denial message to user. */ (void) pam_get_item(pamh, PAM_CONV, (void **) &conversation); if (nowarn == 0 && conversation != NULL) { int err; err = conversation->conv(1, &pmessage, &res, conversation->appdata_ptr); if (debug && err != PAM_SUCCESS) syslog(LOG_AUTH|LOG_DEBUG, "%s: members_only: conversation returned " "error %d (%s).", service, err, pam_strerror(pamh, err)); /* free response (if any) */ if (res != NULL) { if (res->resp) free(res->resp); free(res); } } /* Report denial to system log and return error to caller. */ syslog(LOG_NOTICE | LOG_AUTH, "%s: members_only: " "Connection for %s not allowed from %s", service, user, host); return (PAM_PERM_DENIED); }