1 /* Copyright (c) 2010, 2021, Oracle and/or its affiliates.
2
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License, version 2.0,
5 as published by the Free Software Foundation.
6
7 This program is also distributed with certain software (including
8 but not limited to OpenSSL) that is licensed under separate terms,
9 as designated in a particular file or component or in included license
10 documentation. The authors of MySQL hereby grant you an additional
11 permission to link the program and your derivative works with the
12 separately licensed software that they have included with MySQL.
13
14 This program is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License, version 2.0, for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program; if not, write to the Free Software
21 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
22
23 /**
24 @file
25
26 dialog client authentication plugin with examples
27
28 dialog is a general purpose client authentication plugin, it simply
29 asks the user the question, as provided by the server and reports
30 the answer back to the server. No encryption is involved,
31 the answers are sent in clear text.
32
33 Two examples are provided: two_questions server plugin, that asks
34 the password and an "Are you sure?" question with a reply "yes, of course".
35 It demonstrates the usage of "password" (input is hidden) and "ordinary"
36 (input can be echoed) questions, and how to mark the last question,
37 to avoid an extra roundtrip.
38
39 And three_attempts plugin that gives the user three attempts to enter
40 a correct password. It shows the situation when a number of questions
41 is not known in advance.
42 */
43 #if defined (WIN32) && !defined (RTLD_DEFAULT)
44 # define RTLD_DEFAULT GetModuleHandle(NULL)
45 #endif
46
47 #include <my_global.h>
48 #include <mysql.h>
49 #include <mysql/plugin_auth.h>
50 #include <mysql/client_plugin.h>
51 #include <string.h>
52 #include <stdio.h>
53 #include <stdlib.h>
54
55 #ifdef HAVE_DLFCN_H
56 #include <dlfcn.h>
57 #endif
58
59 #if !defined (_GNU_SOURCE)
60 # define _GNU_SOURCE /* for RTLD_DEFAULT */
61 #endif
62
63 /**
64 first byte of the question string is the question "type".
65 It can be an "ordinary" or a "password" question.
66 The last bit set marks a last question in the authentication exchange.
67 */
68 #define ORDINARY_QUESTION "\2"
69 #define LAST_QUESTION "\3"
70 #define PASSWORD_QUESTION "\4"
71 #define LAST_PASSWORD "\5"
72
73 /********************* SERVER SIDE ****************************************/
74
75 /**
76 dialog demo with two questions, one password and one, the last, ordinary.
77 */
two_questions(MYSQL_PLUGIN_VIO * vio,MYSQL_SERVER_AUTH_INFO * info)78 static int two_questions(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
79 {
80 unsigned char *pkt;
81 int pkt_len;
82
83 /* send a password question */
84 if (vio->write_packet(vio, (const unsigned char *) PASSWORD_QUESTION "Password, please:", 18))
85 return CR_ERROR;
86
87 /* read the answer */
88 if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
89 return CR_ERROR;
90
91 info->password_used= PASSWORD_USED_YES;
92
93 /* fail if the password is wrong */
94 if (strcmp((const char *) pkt, info->auth_string))
95 return CR_ERROR;
96
97 /* send the last, ordinary, question */
98 if (vio->write_packet(vio, (const unsigned char *) LAST_QUESTION "Are you sure ?", 15))
99 return CR_ERROR;
100
101 /* read the answer */
102 if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
103 return CR_ERROR;
104
105 /* check the reply */
106 return strcmp((const char *) pkt, "yes, of course") ? CR_ERROR : CR_OK;
107 }
108
generate_auth_string_hash(char * outbuf,unsigned int * buflen,const char * inbuf,unsigned int inbuflen)109 int generate_auth_string_hash(char *outbuf, unsigned int *buflen,
110 const char *inbuf, unsigned int inbuflen)
111 {
112 /*
113 if buffer specified by server is smaller than the buffer given
114 by plugin then return error
115 */
116 if (*buflen < inbuflen)
117 return 1;
118 strncpy(outbuf, inbuf, inbuflen);
119 *buflen= strlen(inbuf);
120 return 0;
121 }
122
validate_auth_string_hash(char * const inbuf MY_ATTRIBUTE ((unused)),unsigned int buflen MY_ATTRIBUTE ((unused)))123 int validate_auth_string_hash(char* const inbuf MY_ATTRIBUTE((unused)),
124 unsigned int buflen MY_ATTRIBUTE((unused)))
125 {
126 return 0;
127 }
128
set_salt(const char * password MY_ATTRIBUTE ((unused)),unsigned int password_len MY_ATTRIBUTE ((unused)),unsigned char * salt MY_ATTRIBUTE ((unused)),unsigned char * salt_len)129 int set_salt(const char* password MY_ATTRIBUTE((unused)),
130 unsigned int password_len MY_ATTRIBUTE((unused)),
131 unsigned char* salt MY_ATTRIBUTE((unused)),
132 unsigned char* salt_len)
133 {
134 *salt_len= 0;
135 return 0;
136 }
137
138 static struct st_mysql_auth two_handler=
139 {
140 MYSQL_AUTHENTICATION_INTERFACE_VERSION,
141 "dialog", /* requires dialog client plugin */
142 two_questions,
143 generate_auth_string_hash,
144 validate_auth_string_hash,
145 set_salt,
146 AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE
147 };
148
149 /* dialog demo where the number of questions is not known in advance */
three_attempts(MYSQL_PLUGIN_VIO * vio,MYSQL_SERVER_AUTH_INFO * info)150 static int three_attempts(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
151 {
152 unsigned char *pkt;
153 int pkt_len, i;
154
155 for (i= 0; i < 3; i++)
156 {
157 /* send the prompt */
158 if (vio->write_packet(vio,
159 (const unsigned char *) PASSWORD_QUESTION "Password, please:", 18))
160 return CR_ERROR;
161
162 /* read the password */
163 if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
164 return CR_ERROR;
165
166 info->password_used= PASSWORD_USED_YES;
167
168 /*
169 finish, if the password is correct.
170 note, that we did not mark the prompt packet as "last"
171 */
172 if (strcmp((const char *) pkt, info->auth_string) == 0)
173 return CR_OK;
174 }
175
176 return CR_ERROR;
177 }
178
179 static struct st_mysql_auth three_handler=
180 {
181 MYSQL_AUTHENTICATION_INTERFACE_VERSION,
182 "dialog", /* requires dialog client plugin */
183 three_attempts,
184 generate_auth_string_hash,
185 validate_auth_string_hash,
186 set_salt,
187 AUTH_FLAG_PRIVILEGED_USER_FOR_PASSWORD_CHANGE
188 };
189
mysql_declare_plugin(dialog)190 mysql_declare_plugin(dialog)
191 {
192 MYSQL_AUTHENTICATION_PLUGIN,
193 &two_handler,
194 "two_questions",
195 "Sergei Golubchik",
196 "Dialog plugin demo 1",
197 PLUGIN_LICENSE_GPL,
198 NULL,
199 NULL,
200 0x0101,
201 NULL,
202 NULL,
203 NULL,
204 0,
205 },
206 {
207 MYSQL_AUTHENTICATION_PLUGIN,
208 &three_handler,
209 "three_attempts",
210 "Sergei Golubchik",
211 "Dialog plugin demo 2",
212 PLUGIN_LICENSE_GPL,
213 NULL,
214 NULL,
215 0x0101,
216 NULL,
217 NULL,
218 NULL,
219 0,
220 }
221 mysql_declare_plugin_end;
222
223 /********************* CLIENT SIDE ***************************************/
224 /*
225 This plugin performs a dialog with the user, asking questions and
226 reading answers. Depending on the client it may be desirable to do it
227 using GUI, or console, with or without curses, or read answers
228 from a smartcard, for example.
229
230 To support all this variety, the dialog plugin has a callback function
231 "authentication_dialog_ask". If the client has a function of this name
232 dialog plugin will use it for communication with the user. Otherwise
233 a default fgets() based implementation will be used.
234 */
235
236 /**
237 type of the mysql_authentication_dialog_ask function
238
239 @param mysql mysql
240 @param type type of the input
241 1 - ordinary string input
242 2 - password string
243 @param prompt prompt
244 @param buf a buffer to store the use input
245 @param buf_len the length of the buffer
246
247 @retval a pointer to the user input string.
248 It may be equal to 'buf' or to 'mysql->password'.
249 In all other cases it is assumed to be an allocated
250 string, and the "dialog" plugin will free() it.
251 */
252 typedef char *(*mysql_authentication_dialog_ask_t)(struct st_mysql *mysql,
253 int type, const char *prompt, char *buf, int buf_len);
254
255 static mysql_authentication_dialog_ask_t ask;
256
builtin_ask(MYSQL * mysql MY_ATTRIBUTE ((unused)),int type MY_ATTRIBUTE ((unused)),const char * prompt,char * buf,int buf_len)257 static char *builtin_ask(MYSQL *mysql MY_ATTRIBUTE((unused)),
258 int type MY_ATTRIBUTE((unused)),
259 const char *prompt,
260 char *buf, int buf_len)
261 {
262 char *ptr;
263 fputs(prompt, stdout);
264 fputc(' ', stdout);
265 if (fgets(buf, buf_len, stdin) == NULL)
266 return NULL;
267 if ((ptr= strchr(buf, '\n')))
268 *ptr= 0;
269
270 return buf;
271 }
272
273 /**
274 The main function of the dialog plugin.
275
276 Read the prompt, ask the question, send the reply, repeat until
277 the server is satisfied.
278
279 @note
280 1. this plugin shows how a client authentication plugin
281 may read a MySQL protocol OK packet internally - which is important
282 where a number of packets is not known in advance.
283 2. the first byte of the prompt is special. it is not
284 shown to the user, but signals whether it is the last question
285 (prompt[0] & 1 == 1) or not last (prompt[0] & 1 == 0),
286 and whether the input is a password (not echoed).
287 3. the prompt is expected to be sent zero-terminated
288 */
perform_dialog(MYSQL_PLUGIN_VIO * vio,MYSQL * mysql)289 static int perform_dialog(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
290 {
291 unsigned char *pkt, cmd= 0;
292 int pkt_len, res;
293 char reply_buf[1024], *reply;
294
295 do
296 {
297 /* read the prompt */
298 pkt_len= vio->read_packet(vio, &pkt);
299 if (pkt_len < 0)
300 return CR_ERROR;
301
302 if (pkt == 0)
303 {
304 /*
305 in mysql_change_user() the client sends the first packet, so
306 the first vio->read_packet() does nothing (pkt == 0).
307
308 We send the "password", assuming the client knows what it's doing.
309 (in other words, the dialog plugin should be only set as a default
310 authentication plugin on the client if the first question
311 asks for a password - which will be sent in clear text, by the way)
312 */
313 reply= mysql->passwd;
314 }
315 else
316 {
317 cmd= *pkt++;
318
319 /* is it MySQL protocol packet ? */
320 if (cmd == 0 || cmd == 254)
321 return CR_OK_HANDSHAKE_COMPLETE; /* yes. we're done */
322
323 /*
324 asking for a password with an empty prompt means mysql->password
325 otherwise we ask the user and read the reply
326 */
327 if ((cmd >> 1) == 2 && *pkt == 0)
328 reply= mysql->passwd;
329 else
330 reply= ask(mysql, cmd >> 1, (const char *) pkt,
331 reply_buf, sizeof(reply_buf));
332 if (!reply)
333 return CR_ERROR;
334 }
335 /* send the reply to the server */
336 res= vio->write_packet(vio, (const unsigned char *) reply,
337 (int)strlen(reply)+1);
338
339 if (reply != mysql->passwd && reply != reply_buf)
340 free(reply);
341
342 if (res)
343 return CR_ERROR;
344
345 /* repeat unless it was the last question */
346 } while ((cmd & 1) != 1);
347
348 /* the job of reading the ok/error packet is left to the server */
349 return CR_OK;
350 }
351
352 /**
353 initialization function of the dialog plugin
354
355 Pick up the client's authentication_dialog_ask() function, if exists,
356 or fall back to the default implementation.
357 */
358
init_dialog(char * unused1 MY_ATTRIBUTE ((unused)),size_t unused2 MY_ATTRIBUTE ((unused)),int unused3 MY_ATTRIBUTE ((unused)),va_list unused4 MY_ATTRIBUTE ((unused)))359 static int init_dialog(char *unused1 MY_ATTRIBUTE((unused)),
360 size_t unused2 MY_ATTRIBUTE((unused)),
361 int unused3 MY_ATTRIBUTE((unused)),
362 va_list unused4 MY_ATTRIBUTE((unused)))
363 {
364 void *sym= dlsym(RTLD_DEFAULT, "mysql_authentication_dialog_ask");
365 ask= sym ? (mysql_authentication_dialog_ask_t) sym : builtin_ask;
366 return 0;
367 }
368
369 mysql_declare_client_plugin(AUTHENTICATION)
370 "dialog",
371 "Sergei Golubchik",
372 "Dialog Client Authentication Plugin",
373 {0,1,0},
374 "GPL",
375 NULL,
376 init_dialog,
377 NULL,
378 NULL,
379 perform_dialog
380 mysql_end_client_plugin;
381
382