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