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