📜 ⬆️ ⬇️

Authentication via PAM in MySQL

On Habré it was already written that in MySQL it was possible to replace the built-in authentication procedure by downloading the appropriate plugin. In this plugin, you can implement a completely arbitrary user authentication policy, completely moving away from the traditional MySQL scheme username / password in the mysql.user table.

And recently, Oracle released the PAM authentication plugin . When used, the server does not look for passwords in mysql.user , but shifts the authentication task to PAM , a subsystem specifically designed for solving authentication tasks in various applications and contexts, with flexible rules and on-the-fly plug-ins.

Unfortunately, this plugin has several drawbacks. First, it is distributed only with the commercial version of MySQL and its sources are closed. Secondly, it does not support communication between the user and the pam module, and the only possible password authentication is. That, as it were, kills the whole idea.
')
"And why would ..." - I thought. "I will write my pam plugin, with blackjack and whores!"

I am used to working with MySQL sources, so I download 5.5 and unpack it. Although this is not necessary for such a plugin - only the mysql-devel package is sufficient.

Now I am preparing myself a sandbox:
  mysql-5.5.17 $ mkdir plugin / pam_auth
 mysql-5.5.17 $ cd plugin / pam_auth 

As true Jedi, we do not write anything from scratch - so I took auth_socket.c and first removed all unnecessary. It turned out somewhere like this:

#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, /* auth API version */ "dialog", /* client plugin name */ pam_auth /* main auth function */ }; mysql_declare_plugin(pam_auth) { MYSQL_AUTHENTICATION_PLUGIN, /* plugin type */ &pam_auth_handler, /* auth plugin handler */ "pam_auth", /* plugin name */ "Sergei Golubchik", /* author */ "PAM based authentication", /* description */ PLUGIN_LICENSE_GPL, /* license */ NULL, /* init function */ NULL, /* deinit function */ 0x0100, /* version 1.0 */ NULL, /* for SHOW STATUS */ NULL, /* for SHOW VARIABLES */ NULL, /* unused */ 0, /* flags */ } mysql_declare_plugin_end; 


At the very bottom is the handle to the plug-in, it has the same structure for all plug-ins. A little higher is the authentication plug-in handle, and even higher is an empty function, from which I will call pam.

Since the main idea of ​​this pam-plugin is to carry on a dialogue with the user, we must somehow teach the client to get questions from the server and send the answers entered by the user. To do this, MySQL has client plugins - plugins that are loaded into the client (more precisely, libmysqlclient loads them, according to the server instructions). I do not require any exotic from the client plug-in - just repeat the question / answer until the server is completely satisfied. Such a plugin is already there - called “dialog” and, oddly enough, is in the file dialog.c .

This plugin must be specified in the second field of the st_mysql_auth structure, then the server will inform the client that it needs to load the dialog.so and send it everything that my plugin will want to send.

Check. I create CMakeLists.txt (to be honest, I copy from another plugin and slightly correct it), there is only one line:
 MYSQL_ADD_PLUGIN(pam_auth pam_auth.c LINK_LIBRARIES pam) 

and compile
  mysql-5.5.17 $ cmake.  && make 

It works, now it's time to smoke man pam . In MySQL, authentication is quite simple. The plugin gets the name of the user to be authenticated and the vio handler. Vio has the write_packet and read_packet methods that you can use to communicate with the client (in this case, the “dialog” plugin). In pam, everything is a bit more complicated, you need to use the callback function, from which I will call write_packet and read_packet . In general, working with pam looks like this:
  1. initialization - pam_start (here we say which function to call as callback)
  2. authentication - pam_authentificate (somewhere inside and our callback can be called)
  3. check account pam_acct_mgmt
  4. check new username (if pam changed it) - pam_get_item (PAM_USER)
  5. completion pam_end

For any error at any stage, you must immediately proceed to the last step - pam_end . I got this function:
 #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 }; /* get the service name, as specified in CREATE USER ... IDENTIFIED WITH pam_auth AS "service" */ 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; } 

It remains to write the conversation function - the function that pam will call when he wants to ask something. In this function, pam will transfer a list of questions, and she will give him a list of answers. In addition, it is passed to it - as it always happens with callbacks - a pointer for storing additional parameters and status. Since the pointer is one and there are many parameters - I create a structure:
 struct param { unsigned char buf[10240], *ptr; MYSQL_PLUGIN_VIO *vio; }; 

The problem is that the “dialog” plug-in understands only the commands in the form “output this text as a hint, read the string entered by the user, and send it to the server”. And pam has as many as four types of messages, two of which are purely informational and have the semantics of “deduce this, you don't need to enter anything.” Therefore, in my plugin, I accumulate them in the buffer without sending them until I need to enter something. It turns out like this:
 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 there's a message - append it to the buffer */ 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 the message style is *_PROMPT_*, meaning PAM asks a question, send the accumulated text to the client, read the reply */ if (msg[i]->msg_style == PAM_PROMPT_ECHO_OFF || msg[i]->msg_style == PAM_PROMPT_ECHO_ON) { int pkt_len; unsigned char *pkt; /* allocate the response array. freeing it is the responsibility of the caller */ if (*resp == 0) { *resp = calloc(sizeof(struct pam_response), n); if (*resp == 0) return PAM_BUF_ERR; } /* dialog plugin interprets the first byte of the packet as the magic number. 2 means "read the input with the echo enabled" 4 means "password-like input, echo disabled" C'est la vie. */ 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; /* allocate and copy the reply to the response array */ (*resp)[i].resp= strndup((char*)pkt, pkt_len); param->ptr = param->buf + 1; } } return PAM_SUCCESS; } 

That's all. I collect, install - and it does not work. It turns out that because of the bug 60745, clients cannot download the dialog plugin. Well, the solution is obvious.
  mv auth.so dialog.so 

and you can authenticate with MySQL, for example, using S / Key:
  $ mysql
 challenge otp-md5 99 th91334
 password:
 (turning echo on)
 pasword: OMEN US HORN OMIT BACK AHOY
 mysql>

Source: https://habr.com/ru/post/131410/


All Articles