Habréでは、MySQLで対応するプラグインをロードすることにより、組み込みの認証手順を置き換える機会があることがすでに
書かれています。 このプラグインでは、
mysql.userテーブルのMySQLの従来の
ユーザー名 /
パスワードスキームから完全に離れた、完全に任意のユーザー認証ポリシーを実装
できます。
最近、Oracleは
PAM認証プラグインをリリースしました。 使用する場合、サーバーは
mysql.userでパスワードを検索しませんが、柔軟なカスタムルールとプラグインをオンザフライで使用して、さまざまなアプリケーションとコンテキストの認証問題を解決するために特別に設計されたサブシステムである
PAMに認証タスクを転送します。
残念ながら、このプラグインにはいくつかの欠点があります。 まず、商用バージョンのMySQLでのみ配布され、そのソースは閉じられています。 第二に、ユーザーとpam-module間の通信をサポートしておらず、パスワード認証のみが可能なものです。
それは、いわば、全体のアイデアを殺します。「でもなぜ...」と思いました。 「私はpam-pluginをブラックジャックと売春婦で書きます!」
私はMySQLソースの操作に慣れているため、5.5をダウンロードして解凍します。 これはそのようなプラグインには必要ありませんが、
mysql-develパッケージだけで十分です。
今、私は自分自身でサンドボックスを準備しています:
mysql-5.5.17 $ mkdirプラグイン/ pam_auth
mysql-5.5.17 $ cdプラグイン/ pam_auth
真のジェダイとして、私たちはゼロから何も書いていません-だから私は
auth_socket.cを取り、最初は
不要なものをすべて削除しました。 次のようになりました。
#include <mysql/plugin_auth.h> static int pam_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) { } static struct st_mysql_auth pam_auth_handler = { MYSQL_AUTHENTICATION_INTERFACE_VERSION, "dialog", pam_auth }; mysql_declare_plugin(pam_auth) { MYSQL_AUTHENTICATION_PLUGIN, &pam_auth_handler, "pam_auth", "Sergei Golubchik", "PAM based authentication", PLUGIN_LICENSE_GPL, NULL, NULL, 0x0100, NULL, NULL, NULL, 0, } mysql_declare_plugin_end;
一番下のプラグイン記述子は、すべてのプラグインに対して同じ構造を持っています。 もう少し高いのは認証プラグインのハンドルで、さらに高いのは空の関数です。この関数からpamを呼び出します。
このpam-pluginの主なアイデアはユーザーとの対話を行うことなので、サーバーから質問を受け取る方法をクライアントに何らかの方法で教え、ユーザーが入力した回答を送信する必要があります。 このため、MySQLにはクライアントプラグインがあります-クライアントにロードされるプラグイン(より正確には、サーバーの指示に従って
libmysqlclientによってロードされます)。 クライアントプラグインからのエキゾチックは必要ありません-サーバーが完全に満足するまで質問/回答を繰り返してください。 そのようなプラグインはすでに存在します-「ダイアログ」と呼ばれ、奇妙なことに、
dialog.cファイルにあります。
このプラグインは
st_mysql_auth構造体の2番目のフィールドで指定する必要があります。サーバーは、
dialog.soを読み込む必要があることをクライアントに伝え、プラグインが送信するすべてのものを送信します。
検証
CMakeLists.txtを作成し
ます (正直に言うと、別のプラグインからコピーして少し修正します)。1行だけです。
MYSQL_ADD_PLUGIN(pam_auth pam_auth.c LINK_LIBRARIES pam)
そしてコンパイル
mysql-5.5.17 $ cmake。 && make
うまく
いきました 。今度は
man pamを吸う時間です。 MySQLでは、認証は非常に簡単です。 プラグインは、認証されるユーザーの名前と
vioハンドラーを受け取ります。
Vioには、クライアント(この場合はダイアログプラグイン)との通信に使用できる
write_packetおよび
read_packetメソッドがあります。 PAMでは、すべてが少し複雑です。コールバック関数を使用する必要があります。コールバック関数から、
write_packetと
read_packetを呼び出します。 一般に、PAMの操作は次のようになります。
- 初期化-pam_start (ここでコールバックとして呼び出す関数を言います)
- 認証-pam_authentificate (内部のどこかで、コールバックを呼び出すことができます)
- pam_acct_mgmtアカウントの確認
- 新しいユーザー名の確認(PAMが変更した場合) -pam_get_item(PAM_USER)
- pam_endの完了
どの段階でもエラーが発生した場合は、すぐに最後のステップ
pam_endに進む必要があります。 私はここでそのような機能を得ました:
#include <string.h> #include <security/pam_modules.h> #include <security/pam_appl.h> static int conv(int n, const struct pam_message **msg, struct pam_response **resp, void *data) { } #define DO_PAM(X) \ do { \ status = (X); \ if (status != PAM_SUCCESS) \ goto ret; \ } while(0) static int pam_auth(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info) { pam_handle_t *pamh = NULL; int status; const char *new_username; struct param param; struct pam_conv c = { &conv, ¶m }; const char *service = info->auth_string ? info->auth_string : "mysql"; param.ptr = param.buf + 1; param.vio = vio; DO_PAM(pam_start(service, info->user_name, &c, &pamh)); DO_PAM(pam_authenticate (pamh, 0)); DO_PAM(pam_acct_mgmt(pamh, 0)); DO_PAM(pam_get_item(pamh, PAM_USER, (const void**)&new_username)); if (new_username) strncpy(info->authenticated_as, new_username, sizeof(info->authenticated_as)); ret: pam_end(pamh, status); return status == PAM_SUCCESS ? CR_OK : CR_ERROR; }
会話関数( pamが何かを尋ねたいときに呼び出す
関数)を記述することは残っています。 Pamはこの関数に質問のリストを送信し、回答のリストを提供します。 さらに、コールバックの場合と同様に、追加のパラメーターとステータスを保存するためにポインターが渡されます。 ポインターが1つだけで、パラメーターが多数あるため、構造体を作成します。
struct param { unsigned char buf[10240], *ptr; MYSQL_PLUGIN_VIO *vio; };
問題は、「ダイアログ」プラグインが「このテキストをヒントとして表示し、ユーザーが入力した行を読み取り、サーバーに送信する」という形式のコマンドのみを理解することです。 しかし、pamには最大4種類のメッセージがあり、そのうち2つは単なる情報であり、「これを出力して、何も入力しない」というセマンティクスを持っています。 したがって、プラグインでは、何かを入力するまで送信せずにバッファーに蓄積します。 次のようになります。
static int conv(int n, const struct pam_message **msg, struct pam_response **resp, void *data) { struct param *param = (struct param *)data; unsigned char *end = param->buf + sizeof(param->buf) - 1; int i; for (i= 0; i < n; i++) { if (msg[i]->msg) { int len = strlen(msg[i]->msg); if (len > end - param->ptr) len = end - param->ptr; memcpy(param->ptr, msg[i]->msg, len); param->ptr+= len; *(param->ptr)++ = '\n'; } if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || msg[i]->msg_style == PAM_PROMPT_ECHO_ON) { int pkt_len; unsigned char *pkt; if (*resp == 0) { *resp = calloc(sizeof(struct pam_response), n); if (*resp == 0) return PAM_BUF_ERR; } param->buf[0] = msg[i]->msg_style == PAM_PROMPT_ECHO_ON ? 2 : 4; if (param->vio->write_packet(param->vio, param->buf, param->ptr - param->buf - 1)) return PAM_CONV_ERR; pkt_len = param->vio->read_packet(param->vio, &pkt); if (pkt_len < 0) return PAM_CONV_ERR; (*resp)[i].resp= strndup((char*)pkt, pkt_len); param->ptr = param->buf + 1; } } return PAM_SUCCESS; }
実際、それがすべてです。 収集してインストールしますが、動作しません。 バグ
60745により
、クライアントは「ダイアログ」プラグインをダウンロードできないことが
わかりました。 さて、解決策は明らかです
mv auth.so dialog.so
また、たとえばS / Keyを使用して、MySQLで認証できます。
$ mysql
チャレンジotp-md5 99 th91334
パスワード:
(エコーをオンにする)
パスワード:OMEN US HORN OMIT BACK AHOY
mysql>