4
This program is free software; you can redistribute it and/or modify
5
it under the terms of the GNU General Public License as published by
6
the Free Software Foundation; version 2 of the License.
8
This program is distributed in the hope that it will be useful,
9
but WITHOUT ANY WARRANTY; without even the implied warranty of
10
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11
GNU General Public License for more details.
13
You should have received a copy of the GNU General Public License
14
along with this program; if not, write to the Free Software
15
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21
PAM authentication plugin
23
A general-purpose PAM authentication plugin for MySQL. Acts as a mediator
24
between the MySQL server, the MySQL client, and the PAM backend. Consists of
25
both the server and the client plugin.
27
The server plugin requests authentication from the PAM backend, forwards any
28
requests and messages from the PAM backend over the wire to the client (in
29
cleartext) and reads back any replies for the backend.
31
The client plugin inputs from the TTY any requested info/passwords as
32
requested and sends them back over the wire to the server.
34
This plugin does not encrypt the communication channel in any way. If this is
35
required, a SSL connection should be used.
37
To use this plugin for one particular user, install the plugin and specify it
38
at user's creation time (TODO: tested with localhost only):
40
CREATE USER 'username'@'hostname' IDENTIFIED WITH auth_pam_server;
42
Alternatively UPDATE the mysql.user table to set the plugin value for an
48
#include <security/pam_appl.h>
49
#include <security/pam_modules.h>
50
#include <security/pam_misc.h>
52
#define MYSQL_DYNAMIC_PLUGIN
54
/* Define these macro ourselves, so we don't have to include my_global.h and
55
can compile against unconfigured MySQL source tree. */
58
#define MY_ASSERT_UNREACHABLE() assert(0)
60
#include <mysql/plugin.h>
61
#include <mysql/plugin_auth.h>
62
#include <mysql/client_plugin.h>
64
#include <unistd.h> /* getpass() */
66
/* The server plugin */
68
/** The MySQL service name for PAM configuration */
69
static const char* service_name= "mysqld";
71
/** The maximum length of MYSQL_SERVER_AUTH_INFO::external_user field.
72
Shouldn't be hardcoded in the plugin_auth.h but it is. */
73
enum { max_auth_info_external_user_len = 512 };
75
static int valid_pam_msg_style (int pam_msg_style)
77
switch (pam_msg_style)
79
case PAM_PROMPT_ECHO_OFF:
80
case PAM_PROMPT_ECHO_ON:
89
/* Do not use any of MySQL client-server protocol reserved values, i.e. \0 */
90
static char pam_msg_style_to_char (int pam_msg_style)
92
switch (pam_msg_style)
94
case PAM_PROMPT_ECHO_OFF: return '\2';
95
case PAM_PROMPT_ECHO_ON: return '\3';
96
case PAM_ERROR_MSG: return '\4';
97
case PAM_TEXT_INFO: return '\5';
99
MY_ASSERT_UNREACHABLE();
104
static void free_pam_response (struct pam_response ** resp, int n)
107
for (i = 0; i < n; i++)
109
free((*resp)[i].resp);
115
static int vio_server_conv (int num_msg, const struct pam_message **msg,
116
struct pam_response ** resp, void *appdata_ptr)
120
MYSQL_PLUGIN_VIO *vio= NULL;
122
if (appdata_ptr == NULL)
124
MY_ASSERT_UNREACHABLE();
127
vio= (MYSQL_PLUGIN_VIO *)appdata_ptr;
129
*resp = calloc (sizeof (struct pam_response), num_msg);
133
for (i = 0; i < num_msg; i++)
137
if (!valid_pam_msg_style(msg[i]->msg_style))
139
free_pam_response(resp, i);
143
/* Format the message. The first byte is the message type, followed by the
144
NUL-terminated message string itself. */
145
buf= malloc(strlen(msg[i]->msg) + 2);
148
free_pam_response(resp, i);
151
buf[0]= pam_msg_style_to_char(msg[i]->msg_style);
152
strcpy(buf + 1, msg[i]->msg);
154
/* Write the message. */
155
if (vio->write_packet(vio, (unsigned char *)buf, strlen(buf) + 1))
158
free_pam_response(resp, i);
163
/* Is the answer expected? */
164
if ((msg[i]->msg_style != PAM_PROMPT_ECHO_ON)
165
&& (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF))
168
/* Read the answer */
169
if ((pkt_len= vio->read_packet(vio, (unsigned char **)&buf)) < 0)
171
free_pam_response(resp, i);
175
(*resp)[i].resp= malloc(pkt_len + 1);
176
if ((*resp)[i].resp == NULL)
178
free_pam_response(resp, i);
181
strncpy((*resp)[i].resp, buf, pkt_len);
182
(*resp)[i].resp[pkt_len]= '\0';
187
static int authenticate_user_with_pam_server (MYSQL_PLUGIN_VIO *vio,
188
MYSQL_SERVER_AUTH_INFO *info)
190
pam_handle_t *pam_handle;
191
struct pam_conv conv_func_info= { &vio_server_conv, vio };
193
char *external_user_name;
195
/* Impossible to tell if PAM will use passwords or something else */
196
info->password_used= PASSWORD_USED_NO_MENTION;
198
error= pam_start(service_name,
199
(info->authenticated_as && info->authenticated_as[0])
200
? info->authenticated_as : NULL,
201
&conv_func_info, &pam_handle);
202
if (error != PAM_SUCCESS)
205
error= pam_set_item(pam_handle, PAM_RHOST, info->host_or_ip);
206
if (error != PAM_SUCCESS)
208
pam_end(pam_handle, error);
212
error= pam_authenticate(pam_handle, 0);
213
if (error != PAM_SUCCESS)
215
pam_end(pam_handle, error);
219
error= pam_acct_mgmt(pam_handle, 0);
220
if (error != PAM_SUCCESS)
222
pam_end(pam_handle, error);
226
/* Send end-of-auth message */
227
if (vio->write_packet(vio, (const unsigned char *)"\0", 1))
229
pam_end(pam_handle, error);
233
/* Get the authenticated user name from PAM */
234
error= pam_get_item(pam_handle, PAM_USER, (void *)&external_user_name);
235
if (error != PAM_SUCCESS)
237
pam_end(pam_handle, error);
241
/* Check if user name from PAM is the same as provided for MySQL. If
242
different, set @@external_user for the current session to the one provided by
244
if (strcmp(info->authenticated_as, external_user_name))
246
strncpy(info->external_user, external_user_name,
247
max_auth_info_external_user_len);
250
error= pam_end(pam_handle, error);
251
if (error != PAM_SUCCESS)
257
static struct st_mysql_auth pam_auth_handler=
259
MYSQL_AUTHENTICATION_INTERFACE_VERSION,
261
&authenticate_user_with_pam_server
264
mysql_declare_plugin(auth_pam)
266
MYSQL_AUTHENTICATION_PLUGIN,
270
"PAM authentication plugin",
279
mysql_declare_plugin_end;
281
/* The client plugin */
285
static int authenticate_user_with_pam_client (MYSQL_PLUGIN_VIO *vio,
286
struct st_mysql *mysql
287
__attribute__((unused)))
293
char fgets_buf[1024];
298
if ((pkt_len= vio->read_packet(vio, (unsigned char **)&buf)) < 0)
301
/* The first byte is the message type, followed by the message itself. */
304
case '\2': /* PAM_PROMPT_ECHO_OFF */
305
/* TODO: getpass not thread safe. Probably not a big deal in the mysql
306
client program, but may be missing on non-glibc systems. */
307
getpass_input= getpass(&buf[1]);
308
reply= strdup(getpass_input);
311
if (vio->write_packet(vio, (unsigned char *)reply, strlen(reply) + 1))
319
case '\3': /* PAM_PROMPT_ECHO_ON */
320
fputs(&buf[1], stdout);
322
reply= fgets(fgets_buf, sizeof(fgets_buf), stdin);
329
if ((ptr= strchr(buf, '\n')))
332
fgets_buf[sizeof(fgets_buf) - 1]= '\0';
333
if (vio->write_packet(vio, (unsigned char *)fgets_buf,
334
strlen(fgets_buf) + 1))
338
case '\4': /* PAM_ERROR_MSG */
339
fprintf(stderr, "ERROR %s\n", &buf[1]);
342
case '\5': /* PAM_TEXT_INFO */
343
printf("%s\n", &buf[1]);
347
return CR_OK_HANDSHAKE_COMPLETE;
355
/* Should not come here */
356
MY_ASSERT_UNREACHABLE();
360
mysql_declare_client_plugin(AUTHENTICATION)
363
"PAM authentication plugin",
370
&authenticate_user_with_pam_client
371
mysql_end_client_plugin;