~sergei.glushchenko/percona-pam-for-mysql/rem-deps

« back to all changes in this revision

Viewing changes to auth_pam.c

  • Committer: Laurynas Biveinis
  • Date: 2011-06-16 04:23:27 UTC
  • Revision ID: laurynas.biveinis@percona.com-20110616042327-a7790cgmzfr0jrb6
First version of PAM authentication plugin for MySQL 5.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/*
 
2
(C) 2011 Percona Inc.
 
3
 
 
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.
 
7
 
 
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.
 
12
 
 
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
 
16
*/
 
17
 
 
18
/**
 
19
 @file
 
20
 
 
21
 PAM authentication plugin
 
22
 
 
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.
 
26
 
 
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.
 
30
 
 
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.
 
33
 
 
34
 This plugin does not encrypt the communication channel in any way.  If this is
 
35
 required, a SSL connection should be used.
 
36
 
 
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):
 
39
 
 
40
 CREATE USER 'username'@'hostname' IDENTIFIED WITH auth_pam_server;
 
41
 
 
42
 Alternatively UPDATE the mysql.user table to set the plugin value for an
 
43
 existing user.
 
44
*/
 
45
 
 
46
#include <assert.h>
 
47
 
 
48
#include <security/pam_appl.h>
 
49
#include <security/pam_modules.h>
 
50
#include <security/pam_misc.h>
 
51
 
 
52
#define MYSQL_DYNAMIC_PLUGIN
 
53
 
 
54
/* Define these macro ourselves, so we don't have to include my_global.h and
 
55
can compile against unconfigured MySQL source tree.  */
 
56
#define STDCALL
 
57
 
 
58
#define MY_ASSERT_UNREACHABLE() assert(0)
 
59
 
 
60
#include <mysql/plugin.h>
 
61
#include <mysql/plugin_auth.h>
 
62
#include <mysql/client_plugin.h>
 
63
 
 
64
#include <unistd.h> /* getpass() */
 
65
 
 
66
/* The server plugin */
 
67
 
 
68
/** The MySQL service name for PAM configuration */
 
69
static const char* service_name= "mysqld";
 
70
 
 
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 };
 
74
 
 
75
static int valid_pam_msg_style (int pam_msg_style)
 
76
{
 
77
  switch (pam_msg_style)
 
78
  {
 
79
  case PAM_PROMPT_ECHO_OFF:
 
80
  case PAM_PROMPT_ECHO_ON:
 
81
  case PAM_ERROR_MSG:
 
82
  case PAM_TEXT_INFO:
 
83
    return 1;
 
84
  default:
 
85
    return 0;
 
86
  }
 
87
}
 
88
 
 
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)
 
91
{
 
92
  switch (pam_msg_style)
 
93
  {
 
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';
 
98
  default:
 
99
    MY_ASSERT_UNREACHABLE();
 
100
    return '\1';
 
101
  }
 
102
}
 
103
 
 
104
static void free_pam_response (struct pam_response ** resp, int n)
 
105
{
 
106
  int i;
 
107
  for (i = 0; i < n; i++)
 
108
  {
 
109
    free((*resp)[i].resp);
 
110
  }
 
111
  free(*resp);
 
112
  *resp= NULL;
 
113
}
 
114
 
 
115
static int vio_server_conv (int num_msg, const struct pam_message **msg,
 
116
                            struct pam_response ** resp, void *appdata_ptr)
 
117
{
 
118
  int i;
 
119
  int pkt_len;
 
120
  MYSQL_PLUGIN_VIO *vio= NULL;
 
121
 
 
122
  if (appdata_ptr == NULL)
 
123
  {
 
124
    MY_ASSERT_UNREACHABLE();
 
125
    return PAM_CONV_ERR;
 
126
  }
 
127
  vio= (MYSQL_PLUGIN_VIO *)appdata_ptr;
 
128
 
 
129
  *resp = calloc (sizeof (struct pam_response), num_msg);
 
130
  if (*resp == NULL)
 
131
    return PAM_BUF_ERR;
 
132
 
 
133
  for (i = 0; i < num_msg; i++)
 
134
  {
 
135
    char *buf;
 
136
 
 
137
    if (!valid_pam_msg_style(msg[i]->msg_style))
 
138
    {
 
139
      free_pam_response(resp, i);
 
140
      return PAM_CONV_ERR;
 
141
    }
 
142
 
 
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);
 
146
    if (!buf)
 
147
    {
 
148
      free_pam_response(resp, i);
 
149
      return PAM_BUF_ERR;
 
150
    }
 
151
    buf[0]= pam_msg_style_to_char(msg[i]->msg_style);
 
152
    strcpy(buf + 1, msg[i]->msg);
 
153
 
 
154
    /* Write the message.  */
 
155
    if (vio->write_packet(vio, (unsigned char *)buf, strlen(buf) + 1))
 
156
    {
 
157
      free(buf);
 
158
      free_pam_response(resp, i);
 
159
      return PAM_CONV_ERR;
 
160
    }
 
161
    free(buf);
 
162
 
 
163
    /* Is the answer expected? */
 
164
    if ((msg[i]->msg_style != PAM_PROMPT_ECHO_ON)
 
165
        && (msg[i]->msg_style != PAM_PROMPT_ECHO_OFF))
 
166
      continue;
 
167
 
 
168
    /* Read the answer */
 
169
    if ((pkt_len= vio->read_packet(vio, (unsigned char **)&buf)) < 0)
 
170
    {
 
171
      free_pam_response(resp, i);
 
172
      return PAM_CONV_ERR;
 
173
    }
 
174
 
 
175
    (*resp)[i].resp= malloc(pkt_len + 1);
 
176
    if ((*resp)[i].resp == NULL)
 
177
    {
 
178
      free_pam_response(resp, i);
 
179
      return PAM_BUF_ERR;
 
180
    }
 
181
    strncpy((*resp)[i].resp, buf, pkt_len);
 
182
    (*resp)[i].resp[pkt_len]= '\0';
 
183
  }
 
184
  return PAM_SUCCESS;
 
185
}
 
186
 
 
187
static int authenticate_user_with_pam_server (MYSQL_PLUGIN_VIO *vio,
 
188
                                              MYSQL_SERVER_AUTH_INFO *info)
 
189
{
 
190
  pam_handle_t *pam_handle;
 
191
  struct pam_conv conv_func_info= { &vio_server_conv, vio };
 
192
  int error;
 
193
  char *external_user_name;
 
194
 
 
195
  /* Impossible to tell if PAM will use passwords or something else */
 
196
  info->password_used= PASSWORD_USED_NO_MENTION;
 
197
 
 
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)
 
203
    return CR_ERROR;
 
204
 
 
205
  error= pam_set_item(pam_handle, PAM_RHOST, info->host_or_ip);
 
206
  if (error != PAM_SUCCESS)
 
207
  {
 
208
    pam_end(pam_handle, error);
 
209
    return CR_ERROR;
 
210
  }
 
211
 
 
212
  error= pam_authenticate(pam_handle, 0);
 
213
  if (error != PAM_SUCCESS)
 
214
  {
 
215
    pam_end(pam_handle, error);
 
216
    return CR_ERROR;
 
217
  }
 
218
 
 
219
  error= pam_acct_mgmt(pam_handle, 0);
 
220
  if (error != PAM_SUCCESS)
 
221
  {
 
222
    pam_end(pam_handle, error);
 
223
    return CR_ERROR;
 
224
  }
 
225
 
 
226
  /* Send end-of-auth message */
 
227
  if (vio->write_packet(vio, (const unsigned char *)"\0", 1))
 
228
  {
 
229
    pam_end(pam_handle, error);
 
230
    return CR_ERROR;
 
231
  }
 
232
 
 
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)
 
236
  {
 
237
    pam_end(pam_handle, error);
 
238
    return CR_ERROR;
 
239
  }
 
240
 
 
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
 
243
  PAM.  */
 
244
  if (strcmp(info->authenticated_as, external_user_name))
 
245
  {
 
246
    strncpy(info->external_user, external_user_name,
 
247
            max_auth_info_external_user_len);
 
248
  }
 
249
 
 
250
  error= pam_end(pam_handle, error);
 
251
  if (error != PAM_SUCCESS)
 
252
    return CR_ERROR;
 
253
 
 
254
  return CR_OK;
 
255
}
 
256
 
 
257
static struct st_mysql_auth pam_auth_handler=
 
258
{
 
259
  MYSQL_AUTHENTICATION_INTERFACE_VERSION,
 
260
  "auth_pam",
 
261
  &authenticate_user_with_pam_server
 
262
};
 
263
 
 
264
mysql_declare_plugin(auth_pam)
 
265
{
 
266
  MYSQL_AUTHENTICATION_PLUGIN,
 
267
  &pam_auth_handler,
 
268
  "auth_pam_server",
 
269
  "Percona, Inc.",
 
270
  "PAM authentication plugin",
 
271
  PLUGIN_LICENSE_GPL,
 
272
  NULL,
 
273
  NULL,
 
274
  0x0001,
 
275
  NULL,
 
276
  NULL,
 
277
  NULL
 
278
}
 
279
mysql_declare_plugin_end;
 
280
 
 
281
/* The client plugin */
 
282
 
 
283
struct st_mysql;
 
284
 
 
285
static int authenticate_user_with_pam_client (MYSQL_PLUGIN_VIO *vio,
 
286
                                              struct st_mysql *mysql
 
287
                                              __attribute__((unused)))
 
288
{
 
289
  char *buf;
 
290
  int pkt_len;
 
291
  char *getpass_input;
 
292
  char *reply;
 
293
  char fgets_buf[1024];
 
294
  char *ptr;
 
295
 
 
296
  do {
 
297
 
 
298
    if ((pkt_len= vio->read_packet(vio, (unsigned char **)&buf)) < 0)
 
299
      return CR_ERROR;
 
300
 
 
301
    /* The first byte is the message type, followed by the message itself.  */
 
302
    switch (buf[0])
 
303
    {
 
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);
 
309
      if (!reply)
 
310
        return CR_ERROR;
 
311
      if (vio->write_packet(vio, (unsigned char *)reply, strlen(reply) + 1))
 
312
      {
 
313
        free(reply);
 
314
        return CR_ERROR;
 
315
      }
 
316
      free(reply);
 
317
      break;
 
318
 
 
319
    case '\3': /* PAM_PROMPT_ECHO_ON */
 
320
      fputs(&buf[1], stdout);
 
321
      fputc(' ', stdout);
 
322
      reply= fgets(fgets_buf, sizeof(fgets_buf), stdin);
 
323
      if (reply == NULL)
 
324
      {
 
325
        if (ferror(stdin))
 
326
          return CR_ERROR;
 
327
        fgets_buf[0]= '\0';
 
328
      }
 
329
      if ((ptr= strchr(buf, '\n')))
 
330
        *ptr= '\0';
 
331
      else
 
332
        fgets_buf[sizeof(fgets_buf) - 1]= '\0';
 
333
      if (vio->write_packet(vio, (unsigned char *)fgets_buf,
 
334
                            strlen(fgets_buf) + 1))
 
335
        return CR_ERROR;
 
336
      break;
 
337
 
 
338
    case '\4': /* PAM_ERROR_MSG */
 
339
      fprintf(stderr, "ERROR %s\n", &buf[1]);
 
340
      break;
 
341
 
 
342
    case '\5': /* PAM_TEXT_INFO */
 
343
      printf("%s\n", &buf[1]);
 
344
      break;
 
345
 
 
346
    case '\0':
 
347
      return CR_OK_HANDSHAKE_COMPLETE;
 
348
 
 
349
    default:
 
350
      return CR_ERROR;
 
351
    }
 
352
  }
 
353
  while (1);
 
354
 
 
355
  /* Should not come here */
 
356
  MY_ASSERT_UNREACHABLE();
 
357
  return CR_ERROR;
 
358
}
 
359
 
 
360
mysql_declare_client_plugin(AUTHENTICATION)
 
361
  "auth_pam",
 
362
  "Percona, Inc.",
 
363
  "PAM authentication plugin",
 
364
  {0,1,0},
 
365
  "GPL",
 
366
  NULL,
 
367
  NULL, /* init */
 
368
  NULL, /* deinit */
 
369
  NULL, /* options */
 
370
  &authenticate_user_with_pam_client
 
371
mysql_end_client_plugin;