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