1 /* Copyright (c) 2010, 2016, Oracle and/or its affiliates. All rights reserved.
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 #if !defined (_GNU_SOURCE)
56 # define _GNU_SOURCE /* for RTLD_DEFAULT */
57 #endif
58
59 /**
60 first byte of the question string is the question "type".
61 It can be an "ordinary" or a "password" question.
62 The last bit set marks a last question in the authentication exchange.
63 */
64 #define ORDINARY_QUESTION "\2"
65 #define LAST_QUESTION "\3"
66 #define PASSWORD_QUESTION "\4"
67 #define LAST_PASSWORD "\5"
68
69 /********************* SERVER SIDE ****************************************/
70
71 /**
72 dialog demo with two questions, one password and one, the last, ordinary.
73 */
two_questions(MYSQL_PLUGIN_VIO * vio,MYSQL_SERVER_AUTH_INFO * info)74 static int two_questions(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
75 {
76 unsigned char *pkt;
77 int pkt_len;
78
79 /* send a password question */
80 if (vio->write_packet(vio, (const unsigned char *) PASSWORD_QUESTION "Password, please:", 18))
81 return CR_ERROR;
82
83 /* read the answer */
84 if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
85 return CR_ERROR;
86
87 info->password_used= PASSWORD_USED_YES;
88
89 /* fail if the password is wrong */
90 if (strcmp((const char *) pkt, info->auth_string))
91 return CR_ERROR;
92
93 /* send the last, ordinary, question */
94 if (vio->write_packet(vio, (const unsigned char *) LAST_QUESTION "Are you sure ?", 15))
95 return CR_ERROR;
96
97 /* read the answer */
98 if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
99 return CR_ERROR;
100
101 /* check the reply */
102 return strcmp((const char *) pkt, "yes, of course") ? CR_ERROR : CR_OK;
103 }
104
105 static struct st_mysql_auth two_handler=
106 {
107 MYSQL_AUTHENTICATION_INTERFACE_VERSION,
108 "dialog", /* requires dialog client plugin */
109 two_questions
110 };
111
112 /* dialog demo where the number of questions is not known in advance */
three_attempts(MYSQL_PLUGIN_VIO * vio,MYSQL_SERVER_AUTH_INFO * info)113 static int three_attempts(MYSQL_PLUGIN_VIO *vio, MYSQL_SERVER_AUTH_INFO *info)
114 {
115 unsigned char *pkt;
116 int pkt_len, i;
117
118 for (i= 0; i < 3; i++)
119 {
120 /* send the prompt */
121 if (vio->write_packet(vio,
122 (const unsigned char *) PASSWORD_QUESTION "Password, please:", 18))
123 return CR_ERROR;
124
125 /* read the password */
126 if ((pkt_len= vio->read_packet(vio, &pkt)) < 0)
127 return CR_ERROR;
128
129 info->password_used= PASSWORD_USED_YES;
130
131 /*
132 finish, if the password is correct.
133 note, that we did not mark the prompt packet as "last"
134 */
135 if (strcmp((const char *) pkt, info->auth_string) == 0)
136 return CR_OK;
137 }
138
139 return CR_ERROR;
140 }
141
142 static struct st_mysql_auth three_handler=
143 {
144 MYSQL_AUTHENTICATION_INTERFACE_VERSION,
145 "dialog", /* requires dialog client plugin */
146 three_attempts
147 };
148
mysql_declare_plugin(dialog)149 mysql_declare_plugin(dialog)
150 {
151 MYSQL_AUTHENTICATION_PLUGIN,
152 &two_handler,
153 "two_questions",
154 "Sergei Golubchik",
155 "Dialog plugin demo 1",
156 PLUGIN_LICENSE_GPL,
157 NULL,
158 NULL,
159 0x0100,
160 NULL,
161 NULL,
162 NULL,
163 0,
164 },
165 {
166 MYSQL_AUTHENTICATION_PLUGIN,
167 &three_handler,
168 "three_attempts",
169 "Sergei Golubchik",
170 "Dialog plugin demo 2",
171 PLUGIN_LICENSE_GPL,
172 NULL,
173 NULL,
174 0x0100,
175 NULL,
176 NULL,
177 NULL,
178 0,
179 }
180 mysql_declare_plugin_end;
181
182 /********************* CLIENT SIDE ***************************************/
183 /*
184 This plugin performs a dialog with the user, asking questions and
185 reading answers. Depending on the client it may be desirable to do it
186 using GUI, or console, with or without curses, or read answers
187 from a smartcard, for example.
188
189 To support all this variety, the dialog plugin has a callback function
190 "authentication_dialog_ask". If the client has a function of this name
191 dialog plugin will use it for communication with the user. Otherwise
192 a default fgets() based implementation will be used.
193 */
194
195 /**
196 type of the mysql_authentication_dialog_ask function
197
198 @param mysql mysql
199 @param type type of the input
200 1 - ordinary string input
201 2 - password string
202 @param prompt prompt
203 @param buf a buffer to store the use input
204 @param buf_len the length of the buffer
205
206 @retval a pointer to the user input string.
207 It may be equal to 'buf' or to 'mysql->password'.
208 In all other cases it is assumed to be an allocated
209 string, and the "dialog" plugin will free() it.
210 */
211 typedef char *(*mysql_authentication_dialog_ask_t)(struct st_mysql *mysql,
212 int type, const char *prompt, char *buf, int buf_len);
213
214 static mysql_authentication_dialog_ask_t ask;
215
builtin_ask(MYSQL * mysql MY_ATTRIBUTE ((unused)),int type MY_ATTRIBUTE ((unused)),const char * prompt,char * buf,int buf_len)216 static char *builtin_ask(MYSQL *mysql MY_ATTRIBUTE((unused)),
217 int type MY_ATTRIBUTE((unused)),
218 const char *prompt,
219 char *buf, int buf_len)
220 {
221 char *ptr;
222 fputs(prompt, stdout);
223 fputc(' ', stdout);
224 if (fgets(buf, buf_len, stdin) == NULL)
225 return NULL;
226 if ((ptr= strchr(buf, '\n')))
227 *ptr= 0;
228
229 return buf;
230 }
231
232 /**
233 The main function of the dialog plugin.
234
235 Read the prompt, ask the question, send the reply, repeat until
236 the server is satisfied.
237
238 @note
239 1. this plugin shows how a client authentication plugin
240 may read a MySQL protocol OK packet internally - which is important
241 where a number of packets is not known in advance.
242 2. the first byte of the prompt is special. it is not
243 shown to the user, but signals whether it is the last question
244 (prompt[0] & 1 == 1) or not last (prompt[0] & 1 == 0),
245 and whether the input is a password (not echoed).
246 3. the prompt is expected to be sent zero-terminated
247 */
perform_dialog(MYSQL_PLUGIN_VIO * vio,MYSQL * mysql)248 static int perform_dialog(MYSQL_PLUGIN_VIO *vio, MYSQL *mysql)
249 {
250 unsigned char *pkt, cmd= 0;
251 int pkt_len, res;
252 char reply_buf[1024], *reply;
253
254 do
255 {
256 /* read the prompt */
257 pkt_len= vio->read_packet(vio, &pkt);
258 if (pkt_len < 0)
259 return CR_ERROR;
260
261 if (pkt == 0)
262 {
263 /*
264 in mysql_change_user() the client sends the first packet, so
265 the first vio->read_packet() does nothing (pkt == 0).
266
267 We send the "password", assuming the client knows what it's doing.
268 (in other words, the dialog plugin should be only set as a default
269 authentication plugin on the client if the first question
270 asks for a password - which will be sent in clear text, by the way)
271 */
272 reply= mysql->passwd;
273 }
274 else
275 {
276 cmd= *pkt++;
277
278 /* is it MySQL protocol packet ? */
279 if (cmd == 0 || cmd == 254)
280 return CR_OK_HANDSHAKE_COMPLETE; /* yes. we're done */
281
282 /*
283 asking for a password with an empty prompt means mysql->password
284 otherwise we ask the user and read the reply
285 */
286 if ((cmd >> 1) == 2 && *pkt == 0)
287 reply= mysql->passwd;
288 else
289 reply= ask(mysql, cmd >> 1, (const char *) pkt,
290 reply_buf, sizeof(reply_buf));
291 if (!reply)
292 return CR_ERROR;
293 }
294 /* send the reply to the server */
295 res= vio->write_packet(vio, (const unsigned char *) reply,
296 strlen(reply)+1);
297
298 if (reply != mysql->passwd && reply != reply_buf)
299 free(reply);
300
301 if (res)
302 return CR_ERROR;
303
304 /* repeat unless it was the last question */
305 } while ((cmd & 1) != 1);
306
307 /* the job of reading the ok/error packet is left to the server */
308 return CR_OK;
309 }
310
311 /**
312 initialization function of the dialog plugin
313
314 Pick up the client's authentication_dialog_ask() function, if exists,
315 or fall back to the default implementation.
316 */
317
init_dialog(char * unused1 MY_ATTRIBUTE ((unused)),size_t unused2 MY_ATTRIBUTE ((unused)),int unused3 MY_ATTRIBUTE ((unused)),va_list unused4 MY_ATTRIBUTE ((unused)))318 static int init_dialog(char *unused1 MY_ATTRIBUTE((unused)),
319 size_t unused2 MY_ATTRIBUTE((unused)),
320 int unused3 MY_ATTRIBUTE((unused)),
321 va_list unused4 MY_ATTRIBUTE((unused)))
322 {
323 void *sym= dlsym(RTLD_DEFAULT, "mysql_authentication_dialog_ask");
324 ask= sym ? (mysql_authentication_dialog_ask_t) sym : builtin_ask;
325 return 0;
326 }
327
328 mysql_declare_client_plugin(AUTHENTICATION)
329 "dialog",
330 "Sergei Golubchik",
331 "Dialog Client Authentication Plugin",
332 {0,1,0},
333 "GPL",
334 NULL,
335 init_dialog,
336 NULL,
337 NULL,
338 perform_dialog
339 mysql_end_client_plugin;
340
341