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