【Linux】 C++でユーザ認証を行い処理を実行するプログラム

ユーザ認証を指定して特定の処理をするために,この記事では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関数

この関数は,指定したユーザー名とパスワードを用いてユーザー認証を行います.認証は,以下のステップで行われます.

  1. pam_start: PAMライブラリの初期化,およびユーザー名やパスワードを引き渡すconversation関数の設定.
  2. pam_authenticate: ユーザーのパスワードが整合性チェック.
  3. pam_acct_mgmt: アカウントが有効であるかどうかのチェック.(例えば,パスワードが期限切れ,アカウントロック等)
  4. 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ユーザ認証を行うコードを紹介しました.

これはサンプルプログラムですが,実運用ではデーモン化などを行ってソフトウェアにすることもあるでしょう.

よかったらシェアしてね!
目次