ユーザ認証を指定して特定の処理をするために,この記事ではPAM認証を用いた方法を示します.
PAMとは
PAM(Pluggable Authentication Modules)は,ユーザー認証機能をアプリケーションに組み込むための汎用フレームワークです.UNIX系のシステムで汎用的に使用されています.
また,PAMはユーザ認証だけでなく,アカウント管理やセッション管理などの機能も有しており,それぞれモジュール化されています.認証メソッドを個々のモジュールとして提供することで,アプリケーション開発者が特定の認証方式に縛られることなく,必要な認証メソッドを柔軟に選び組み合わせることができます.
すなわち,PAMを利用することでアプリケーション開発と認証メソッドを分離することにより開発がスムーズになります.
https://docs.oracle.com/cd/E19253-01/819-0396/ch3pam-01/index.html
PAMライブラリのインストール方法
「ソースからインストールする方法」と「パッケージマネージャを用いてインストール方法」の二種類の方法を示します.
最新版のPAMが使用したい場合,ソースからインストールします.
簡単に安定したインストールを行いたい場合,パッケージマネージャからインストールすることを推奨します.
ソースからインストールする方法
ソースからインストールする場合は下記記事をご覧ください.
パッケージマネージャからインストールする方法
パッケージマネージャを利用してインストールする場合は,ディストリビューションごとに次のようになります.
Debian系
apt install libpam0g-dev
RHEL系
dnf install pam-devel
プログラム例
ここでは,PAMを用いたユーザ認証をC++で行う例を示します.
PAMは管理者権限でないと実行できないため注意が必要です.
プログラム全体
まずプログラム全体は下記の通りです.
main.cpp
#include <security/pam_appl.h>
#include <pwd.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include <string>
int conversation(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
*resp = (struct pam_response *)malloc(num_msg * sizeof(struct pam_response));
if (*resp == NULL)
return PAM_BUF_ERR;
for (int i = 0; i < num_msg; ++i)
{
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF)
{
resp[i]->resp_retcode = PAM_SUCCESS;
resp[i]->resp = strdup(((std::string *)appdata_ptr)->c_str()); // Copy password
}
else
{
return PAM_CONV_ERR;
}
}
return PAM_SUCCESS;
}
bool AuthenticateUser(const std::string &username, const std::string &password)
{
struct pam_conv conv = {conversation, (void *)&password};
pam_handle_t *pamh;
int retval = pam_start("common-auth", username.c_str(), &conv, &pamh);
if (retval != PAM_SUCCESS)
{
std::cerr << "Failed to start PAM: " << pam_strerror(pamh, retval) << '\n';
return false;
}
retval = pam_authenticate(pamh, 0); // Check password
if (retval != PAM_SUCCESS)
{
std::cerr << "Authentication failed: " << pam_strerror(pamh, retval) << '\n';
return false;
}
retval = pam_acct_mgmt(pamh, 0); // Check that the account is valid
if (retval != PAM_SUCCESS)
{
std::cerr << "Account validation failed: " << pam_strerror(pamh, retval) << '\n';
return false;
}
if (pam_end(pamh, 0) != PAM_SUCCESS)
{
std::cerr << "Failed to release PAM handle.\n";
return false;
}
return true;
}
int main(int argc, char **argv)
{
if (geteuid() != 0)
{
std::cerr << "This program must be run as root. Exiting.\n";
exit(1);
}
std::string username, password;
std::cout << "Username: ";
std::getline(std::cin, username);
std::cout << "Password: ";
std::getline(std::cin, password);
if (!AuthenticateUser(username, password))
{
std::cerr << "Failed to authenticate user.\n";
exit(1);
}
// write your code that after authentication
return 0;
}
conversation関数
この関数は,ユーザーからパスワードの入力を受け付ける役割を果たします.この関数はpam_conv
構造体で指定され、PAM認証処理の中でコールバックされます.
#include <security/pam_appl.h>
#include <pwd.h>
#include <unistd.h>
#include <iostream>
#include <cstring>
#include <string>
int conversation(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr)
{
*resp = (struct pam_response *)malloc(num_msg * sizeof(struct pam_response));
if (*resp == NULL)
return PAM_BUF_ERR;
for (int i = 0; i < num_msg; ++i)
{
if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF)
{
resp[i]->resp_retcode = PAM_SUCCESS;
resp[i]->resp = strdup(((std::string *)appdata_ptr)->c_str()); // Copy password
}
else
{
return PAM_CONV_ERR;
}
}
return PAM_SUCCESS;
}
AuthenticateUser関数
この関数は,指定したユーザー名とパスワードを用いてユーザー認証を行います.認証は,以下のステップで行われます.
pam_start
: PAMライブラリの初期化,およびユーザー名やパスワードを引き渡すconversation
関数の設定.pam_authenticate
: ユーザーのパスワードが整合性チェック.pam_acct_mgmt
: アカウントが有効であるかどうかのチェック.(例えば,パスワードが期限切れ,アカウントロック等)pam_end
: PAMのハンドルをクリーンアップし,リソースを解放.
bool AuthenticateUser(const std::string &username, const std::string &password)
{
struct pam_conv conv = {conversation, (void *)&password};
pam_handle_t *pamh;
int retval = pam_start("common-auth", username.c_str(), &conv, &pamh);
if (retval != PAM_SUCCESS)
{
std::cerr << "Failed to start PAM: " << pam_strerror(pamh, retval) << '\n';
return false;
}
retval = pam_authenticate(pamh, 0); // Check password
if (retval != PAM_SUCCESS)
{
std::cerr << "Authentication failed: " << pam_strerror(pamh, retval) << '\n';
return false;
}
retval = pam_acct_mgmt(pamh, 0); // Check that the account is valid
if (retval != PAM_SUCCESS)
{
std::cerr << "Account validation failed: " << pam_strerror(pamh, retval) << '\n';
return false;
}
if (pam_end(pamh, 0) != PAM_SUCCESS)
{
std::cerr << "Failed to release PAM handle.\n";
return false;
}
return true;
}
main関数
main
関数では,まずroot権限で実行されているかチェックを行います.
次に,標準入力からユーザー名とパスワードを受け取り、AuthenticateUser
関数を呼び出して認証を試みます.
認証が成功した場合,その後のコードが実行されます.認証が失敗した場合,エラーメッセージが表示されてプログラムが終了します.
// write your code that after authentication
の部分を適宜実行したいコードを記述すれば,ユーザ認証を用いたプログラムとなります.
int main(int argc, char **argv)
{
if (geteuid() != 0)
{
std::cerr << "This program must be run as root. Exiting.\n";
exit(1);
}
std::string username, password;
std::cout << "Username: ";
std::getline(std::cin, username);
std::cout << "Password: ";
std::getline(std::cin, password);
if (!AuthenticateUser(username, password))
{
std::cerr << "Failed to authenticate user.\n";
exit(1);
}
// write your code that after authentication
return 0;
}
コンパイル
コンパイルはpamライブラリをリンクすればOKです.
g++ main.cpp -lpam
まとめ
この記事では,Linuxユーザ認証を行うコードを紹介しました.
これはサンプルプログラムですが,実運用ではデーモン化などを行ってソフトウェアにすることもあるでしょう.